Web Hosting Talk







View Full Version : Preventing malicious form injections


BostonGuru
08-11-2006, 10:42 AM
What are some methods to prevent malicious people from creating their own form code on their own page, which has an action to $_POST to one of your websites. I thought about adding a $SERVER_['remoteAddr'] which would check to see if it was being submitted from the form page, but I read that that value is received from the user, and can easily be tampered with.

What are some methods people use to solve this?

horizon
08-11-2006, 11:05 AM
This forum also offers free codings for integrating captcha in your PHP script. It generates random numbers automatically, for each refresh, on your page.

maiahost
08-11-2006, 11:28 AM
horizon is right random number or simple image verification will do the trick. You can get such a sript for free (google it:). You can also do it with a mysql database but that's harder and unnecessary.

horizon
08-11-2006, 11:38 AM
You can also do it with a mysql database but that's harder and unnecessary.


It also uses more SQL ressources just to simply accomplish the task. mySQL would not be a solution period for this case. ;)

$howdy$
08-11-2006, 02:56 PM
Hello,

I don't think the thread starter is asking about methods of preventing spam (color blind people or people bad in simple math), in which captcha is the common method.

I somewhat had a hard time understanding the question based on the post, but I'm concluding that he's talking about SQL injections and/or XSS to an input form field or $_GET variables, based on the title of this thread.

When coming from an input form, just be sure to filter out these characters

"
'
<
>
(
)

As far as them actually using your own created form to fill out data for submittion, they don't have to use it. You can of course check for HTTP referral to ensure that they did come from the actual URL where the form fields are, but there are people with browsers that doesn't submit referrals (uncommon).

In the end, it's about parsing the user data. I never trust user data. I always see them as trying to hack the site, no matter their computer knowledge level.

With PHP I here's my incomplete process for the user submitted data:
* add slashes to them
* decode the URL if need be
* regex for the correct format ie: email, only numbers, etc

Now when parsing this data on the page and to ensure there will be no XSS exploits, here's my process in PHP:
* encode the URL if need be
* strip the slashes
* regex for <, >, (, ), #, ", '
* instead of regex, htmlentities() seems to do the trick

Not 100% secure nor 100% it's not going to break out the (X)HTML layout, but it works so far.

horizon
08-11-2006, 03:01 PM
instead of regex, htmlentities() seems to do the trick

You could also use ereg_replace to eliminate all / most un-desired characters (especially useful when executing SQL injections). ;)

BostonGuru
08-11-2006, 11:51 PM
Thanks for the replies everyone. I am actually trying to protect myself from both spam and malicious injections.

I currently have captcha on my form already, but the problem is that there is always some sort of key passed with the form; either an encrypted version of the graphic value, or a key which an SQL database will use to find the stored graphic value. In either way somebody could look at the source once and get the value of the hidden variable containing the data needed to compare, and then create a form which will submit both that and the text in the graphic as hidden fields.

Regarding avoiding malicious injections:

Can you give an example of what you mean by adding/removing slashes to PHP, and encoding/decoding URLs?


I currently wrote a function which checks variables and will return false if it has anything else besides letters, numbers, _, - , ,., and @. Is there anything that htmlentities or regex would catch that this function wouldn't?

horizon
08-11-2006, 11:59 PM
I currently wrote a function which checks variables and will return false if it has anything else besides letters, numbers, _, - , ,., and @. Is there anything that htmlentities or regex would catch that this function wouldn't?


A good way to find out would be by posting your general function you created here. Then, post the queries which gathers the Form's info to see if they are handling your inputs correctly.

BostonGuru
08-12-2006, 12:12 AM
This is the function


function iScan($string)
{
$validBank = array("a","A","b","B","c","C","d","D","e","E","f","F","g","G","h","H","i","I","j","J","k","K","l","L","m","M","n","N","o","O","p","P","q","Q","r","R","s","S","t","T","u","U","v","V","w","W","x","X","y","Y","z","Z","1","2","3","4","5","6","7","8","9","0");
$symbolFound = 0;
for($i=0;$i<strlen($string);$i++)
{
$curChar = $string{$i};
$validFound = 0;
for($j=0;$j<count($validBank);$j++)
{
if($curChar==$validBank[$j]) $validFound = 1;
}
if($validFound==0) $symbolFound = 1;
}
if($symbolFound==1) $result = FALSE;
else $result = TRUE;
}
This is the code that imports the values and checks them:


if(!iScan($_POST['password'])) $message = 'Invalid hidden scan.';
else $password = $_POST['password'];

Elsewhere in the code it echos the message if any, and the variable $_POST['password'] is only referenced here. Throughout the rest of the code it is referred to as $password, which will only contain a value if the data was clean.

maxymizer
08-12-2006, 12:22 AM
Thanks for the replies everyone. I am actually trying to protect myself from both spam and malicious injections.

I currently have captcha on my form already, but the problem is that there is always some sort of key passed with the form; either an encrypted version of the graphic value, or a key which an SQL database will use to find the stored graphic value. In either way somebody could look at the source once and get the value of the hidden variable containing the data needed to compare, and then create a form which will submit both that and the text in the graphic as hidden fields.


That's obviously a wrong way to pass requried comparision value. How about this:

- generate a random 6 letter/number code
- store it to a session named "captcha_code"
- display that random code via image

After that user submits the form, and you have his/hers input which you compare to a session value.
That way the user cannot see what's inside the session and your form cannot be tampered with. Also, that is the correct way to determine whether the submitter is human or computer.

BostonGuru
08-12-2006, 12:37 AM
you still need to tell the image script where to get the code from correct? You would have to give it the session value, perhaps using $_GET? Wouldn't that make this still tamperable?

maxymizer
08-12-2006, 12:49 AM
you still need to tell the image script where to get the code from correct? You would have to give it the session value, perhaps using $_GET? Wouldn't that make this still tamperable?

I didn't make it clear enough.

You simply generate a random string, that's the first thing you do - then you store it to a session.

After that step, you display a form with an image. Image gets the code from that session, which value is hidden from the end-user.

User submits what he/she saw on the image, and you simply compare that POST value to what you have stored in your $_SESSION variable.

BostonGuru
08-12-2006, 01:17 AM
Ok, I am a little new with sessions, but here is what I gather so far on what I should do:

1. Have session_start(); on the form page.
2. Save a random string to $SESSION_['key'];
3. Have the image call $SESSION_['key'] then print to image
4. Call the $SESSION_['key'] on the page that the form resolves to to compare to the form input.
5. Do a session_destroy(); once the variable has been used.

It says where I am reading that PHP passes the session ID by using cookies. This does not mean that the $SESSION_['key'] variable would be stored in a cookie, just the session ID number that contains the variable on the server, correct?

maxymizer
08-12-2006, 01:55 AM
Yes, only session ID is stored on the client computer and the actual values of that session are stored on the server (usually as flat files in /tmp directory of the server unless specified otherwise but that's completely another subject).

Also, since we're mentioning sessions - session ID can be passed via cookies or via URL (if cookies are for some reason disabled at client computer).

Note: it's $_SESSION, not $SESSION_ ;)

That means you're safe if you use sessions for storing data you want to hide from your users and that are vital for your script.

$howdy$
08-12-2006, 03:41 AM
Since you seem to be only allowing alphanumeric characters you can simplify your function to this:

function iScan($string)
{
if (preg_match('/[a-zA-Z0-9]+/', $string)) {
return true;
}
else {
return false;
}
}

I don't mean to burst your bubble, but whenever you get this problem solved, I just want to point out that this is just less than half the battle. The strength of your CAPTCHA is still questionable.

Whenever you're ready, check this out
http://www.cs.sfu.ca/~mori/research/gimpy/ (http://www.cs.sfu.ca/%7Emori/research/gimpy/)

They broke the EZ-Gimpy CAPTCHA version that Yahoo used to use.

List of the CAPTCHA images that they can compromize
http://www.cs.sfu.ca/~mori/research/gimpy/ez/ (http://www.cs.sfu.ca/%7Emori/research/gimpy/ez/)

Sad how some of them are hardly even "human" readable, yet can be compromized.

$howdy$
08-12-2006, 03:43 AM
Oops. Actually this should do it.

function iScan($string)
{
return preg_match('/[a-zA-Z0-9]+/', $string);
}

horizon
08-12-2006, 07:55 AM
You can also replace this block:


function iScan($string)
{
return preg_match('/[a-zA-Z0-9]+/', $string);
}


With this one:


function iScan($string)
{
return preg_match('/[^a-zA-Z0-9]+/i', $string);
}


;)

BostonGuru
08-12-2006, 11:50 AM
is there a way to tweak that preg_match so that it allows @.-_ and spaces as well?

Also I think the reason yahoo's captcha is so easy to crack, is because they are using dictionary words. The image reader finds the probability of each word in the dictionary set and the returns the one with the highest rate. If the captcha box has random characters in it, it will be MUCH harder to crack.

horizon
08-12-2006, 11:53 AM
is there a way to tweak that preg_match so that it allows @.-_ and spaces as well?

Yes, this has been discovered here:

http://ca.php.net/ereg_replace

Go to: Example 3 on the page. You'll find the exact results you're actually looking for. ;)


Also I think the reason yahoo's captcha is so easy to crack, is because they are using dictionary words. The image reader finds the probability of each word in the dictionary set and the returns the one with the highest rate. If the captcha box has random characters in it, it will be MUCH harder to crack.

Correct. Which is why, I already have an alternative captcha for my own that does NOT depend on dictionnary words. So far, no such SPAMs, flood has been done over my site since this new implementation. ;)

BostonGuru
08-12-2006, 12:16 PM
If I understand this right, I would use:


function iScan($string)
{
return preg_match('/[^a-zA-Z0-9@\-_\.]+/i', $string);
}


It is saying the character can be a-ZA-Z, @, -,_, or., and there is a backslash in front of the - and . to tell the function that those symbols are literal. How would I denote a space character (" ") as a vlid option. Also what does adding i on to the end of the preg_match as you did do?

horizon
08-12-2006, 01:16 PM
One I can answer here:


Also what does adding i on to the end of the preg_match as you did do?


http://ca.php.net/manual/en/function.preg-match.php

Example 1 and Example 2 should help you out to understand further on this question.

As for the others you asked right above, sorry - I'm also wondering the signification of these right now since I don't entirely understand it's purpose but, according to your example above, I think you're close to a solution. ;)

Renard Fin
08-12-2006, 01:51 PM
for protection against sql injections, why not use mysql_real_escape ?

http://php.net/manual/en/function.mysql-real-escape-string.php

horizon
08-12-2006, 01:54 PM
Now, here's an interesting idea. Sorry, I didn't know this was done by PHP DEV Team. Thanks for posting this link. It will, sure, be useful. ;)

$howdy$
08-12-2006, 03:11 PM
for protection against sql injections, why not use mysql_real_escape ?

http://php.net/manual/en/function.mysql-real-escape-string.php
It depends on your logic. That function works only if database connectivity has already been established. If you are not connected to the database, the function will not work.

There are times when you need to evaluate the string for safe database input before the connection.

I think the logic should somewhat be similar to this untested function.

function input_sql($data)
{
if (is_string($data) && !is_numeric($data)) {
return '\'' . $data . '\'';
}
return $data;
}

Correct me if I'm wrong, but aren't you expecting iScan() to return true if the characters you want exists???

Hence:

function iScan($string)
{
// added space " "
return preg_match('/[a-zA-Z0-9@\-_\.\s]+/', $string);
}
would return true. You don't need specify "i" as the lower case because already been defined [a-z].

If you want it to return the opposite just do this.

function iScan($string)
{
// added space " "
return !preg_match('/[a-zA-Z0-9@\-_\.\s]+/', $string);
}

The goal with regular expressions is to give them less work as possible to evaluate the string. The example above gives you an optimized version of getting the reverse boolean output for the function.

If you have a certain string legth that must be met first, I suggest you run that test first so you that you can avoid the regexp test if it fails.
Here's a more optimized way.

function iScan($string)
{
if (strlen($string) !== 6/* or whatever */) {
return false;
}
return !preg_match('/[a-zA-Z0-9@\-_\.\s]+/', $string);
}
Add an extra paramater for the length if you like.

BostonGuru
08-12-2006, 03:15 PM
Sorry to backtrack a bit, but I am trying this sessions method to create an image.

On the form page I have $_SESSION['imageText'] defined as a random 8 char key, then right below it I have <img src="index.php?page=image"> which should display the image with the key on it.

This is the code I have in the index.php file where page==image

$textstr = $_SESSION['imageText'];
$font = 'my_images/COOPBLA.TTF';
$size = rand(10, 12);
$bgurl = rand(1, 3);
$im = ImageCreateFromPNG("my_images/bg".$bgurl.".png");
$angle = rand(-3, 3);
$color = ImageColorAllocate($im, rand(0, 100), rand(0, 100), rand(0, 100));
$textsize = imagettfbbox($size, $angle, $font, $textstr);
$twidth = abs($textsize[2]-$textsize[0]);
$theight = abs($textsize[5]-$textsize[3]);
$x = (imagesx($im)/2)-($twidth/2)+(rand(-20, 20));
$y = (imagesy($im))-($theight/2);
ImageTTFText($im, $size, $angle, $x, $y, $color, $font, $textstr);
header("Content-Type: image/png");
ImagePNG($im);
imagedestroy($im);
exit;

The showing image though is blank, so for some reason the $_SESSION['imageText'] is not being passed to the image file. Any thoughts on why this is?

BostonGuru
08-12-2006, 03:22 PM
I like this code

function iScan($string)
{
if (strlen($string) < 8/* or whatever */) {
return false;
}
return !preg_match('/[a-zA-Z0-9@\-_\.\s]+/', $string);
}

Is \s the literal for a space?

$howdy$
08-12-2006, 03:24 PM
Tip: Try not to be a lazy coder and perform checks. Even for quick tests.


$textstr = $_SESSION['imageText'];
if (empty($textstr)) {
die('Problem here: empty $textstr');
}
...
...
if (!file_exists('my_images/COOPBLA.TTF')) {
die('Problem here: font file');
}
...
...
if (!file_exists("my_images/bg".$bgurl.".png")) {
die('Problem here: background image');
}

Put this die() statement here and see what error you get if you don't want to do the checks above.

...
ImageTTFText($im, $size, $angle, $x, $y, $color, $font, $textstr);
// add ----------------------------
die();
// ----------------------------------
header("Content-Type: image/png");
ImagePNG($im);
imagedestroy($im);
exit;

BostonGuru
08-12-2006, 03:40 PM
Sorry howdy,

I wasn't specific enough in that post. The problem IS that the $_SESSION['imageText'] is blank because when I change $textstr to just a literal such as "test" and load the image, you can see "test" clearly printed in the image. I guess the correct question would be why isn't the session variable being passed to the image php code.

$howdy$
08-12-2006, 04:09 PM
That's probably my fault. I read your post too fast.

I just did a quick test. Maybe you can follow it up and see where you might be going wrong.

test.php

session_start();
$_SESSION['imageText'] = 'sup';
echo '<a href="test2.php">Click here</a>';

test2.php

session_start();
echo $_SESSION['imageText']; // Output: sup

That worked for me. You might not be calling session_start() in the beginning of the page or something.

BostonGuru
08-12-2006, 04:16 PM
Yes i didn't have session_start(); at the begining of my image generation code. Seems to be working now.

That solves that problem :)

Just to touch base again on the preg_match pattern formatting, I noticed you put \s in the acceptable chars area. Is this a way to denote a space as an acceptable characeter?

$howdy$
08-12-2006, 04:36 PM
That's just another way of representing a space. You can get away with this (notice the space at the end):

return preg_match('/[a-zA-Z0-9@\-_\. ]+/', $string);

As for your function here's my test if you really want to know if it works.

superman_test.php

/**
* Accepts the following characters a-zA-Z0-9@-_.\s
*
* @author Superman - "World defender"
* @version none.u.binez
* @param string $string
* @param int $len
* @return bool (true if it pass, false if it fails)
*/
function iScan($string, $len = NULL)
{
if ($len !== NULL) {
if (strlen($string) < $len) {
return false;
}
}
return preg_match('/[a-zA-Z0-9@\-_\.\s]+/', $string);
}

$test_case = 'abcdefghijklmnopqrstuvwxyz';
$test_case .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$test_case .= '01234567890';
$test_case .= '@-._ ';
$test_case_len = strlen($test_case) - 1;

$container = array();
$test_amount = mt_rand(5, 20); // Number of random test case

for ($i = 0; $i < $test_amount; $i++) {
$container[$i] = '';
/*
* Random length
*/
$char_len = mt_rand(0, 32);
/*
* Create the random stuff
*/
for ($j = 0; $j <= $char_len; $j++) {
$container[$i] .= $test_case[mt_rand(0, $test_case_len)];
}

echo 'Test ' . ($i + 1) . ':<br />';
echo 'Using: ' . $container[$i] . '<br />';
if (iScan($container[$i], 8)) {
echo 'Okay<br />';
}
else {
echo 'Uhm...not okay!<br />';
}
echo '----------------------------<br />';
}

BostonGuru
08-12-2006, 04:52 PM
alright ill give it a try. Thanks for the help!

BostonGuru
08-12-2006, 10:43 PM
BACK to the sessions thing; I am trying to setup the disabled cookies fallback by passing the $PHPSESSID variable, but the $PHPSESSID is NULL even when I disable cookies in my browser. Also this is PHP5 so it should have a value by default, unlike PHP4 where you need to define it first.

BostonGuru
08-12-2006, 11:05 PM
scratch that last post; apparently i have "cookies only" enabled on my php.ini

horizon
08-13-2006, 09:44 AM
Is there a way to use this function with $_GET or $_POST ? If so, what would be the correct way to apply the condition ?

BostonGuru
08-13-2006, 12:02 PM
you mean the invalid characters function?

If so all you would do to check a get or post value is to paste the function somewhere into the script, then call the function with the $_GET or $_POST as your input:

ex.

if(!iScan($_POST['variable'])) echo "You have invalid characters.";
else
{
Put statements to be performed is variable is valid.
}

horizon
08-13-2006, 12:56 PM
Looks really good actually. What if I'd like to avoid these characters rather than using them, do I simply reverse the preg_match inside the function ?

BostonGuru
08-13-2006, 01:05 PM
Actually I dont think that would quite work, because as it is, it returns false if one character isn't as listed inside. so if the string was "#dsidshgi" it would return false. If you just reversed the return it would be returning true, even though there are still characters that were inside the list in the string.

I believe what you need to do is replace the [] that are around the characters with (). That will cause it to return false if any of the characters listed are found.

horizon
08-13-2006, 01:30 PM
I believe what you need to do is replace the [] that are around the characters with (). That will cause it to return false if any of the characters listed are found.


Any way to show this technicly ? :)

BostonGuru
08-13-2006, 01:59 PM
What characters do you want to disallow?

horizon
08-13-2006, 02:06 PM
These ones seem to be in response of what I was, also, looking for but I'm trying to assign this over the session_id(). Right now, all I got is 'if the SID is empty' - then replace the characters - such as:


preg_replace('/[^a-z0-9]+/i', '', session_id());


does that really suffice against SQL injections ?

BostonGuru
08-13-2006, 02:13 PM
whats the other variable here? usually preg_replace you have the string that you are reading in, the conditions, and then the replacement variable.

horizon
08-13-2006, 04:30 PM
It comes from a class - stating like this:


$this->session_id = preg_replace('/[^a-z0-9]+/i', '', session_id());

$howdy$
08-13-2006, 05:16 PM
Sorry everyone. I only ran that test once. I forgot to encapsulate the whole thing. Here, this should do it.

function iScan($string, $len = NULL)
{
if ($len !== NULL) {
if (strlen($string) < $len) {
return false;
}
}
return preg_match('/^[a-zA-Z0-9@\-_\.\s]+$/', $string);
}

$howdy$
08-13-2006, 05:31 PM
These ones seem to be in response of what I was, also, looking for but I'm trying to assign this over the session_id(). Right now, all I got is 'if the SID is empty' - then replace the characters - such as:


preg_replace('/[^a-z0-9]+/i', '', session_id());


does that really suffice against SQL injections ?
Yes. You're only permitting characters a-z, A-Z, 0-9. You can't execute any SQL injections with just those characters.

It's a different story though if you're allowing other characters, which is more common.

horizon
08-13-2006, 05:39 PM
Thanks for your last input. I think it's very important to know that this small block is not sufficent. Any idea on how to improve this command ? Since, I'd like to make sure that no SQL injections are done without the proper actions from PHP (without commenting it from the source).

For instance, would this command do:


$this->session_id = preg_replace('/^[a-zA-Z0-9@\-_\.\s]+$/', '', session_id());


?

$howdy$
08-13-2006, 05:57 PM
No, don't change your code. You had it right. The regex I was showing is for his iScan() function.

Consider this case

$var = 'MyName#@#.Is-Super Man <script>JavaScript</script>';
echo preg_replace('/[^a-z0-9]+/i', '', $var);

Output: MyNameIsSuperManscriptJavaScriptscript
It removed anything besides alphanumeric characters.

But you shouldn't directly port the results to your session_id var.

Just check to make sure it qualifies for a valid session id. Like:
32 char length
abdef1234567890 characters only

Hence

function validSession($data = '')
{
if (is_string($data) && !empty($data) && strlen($data) === 32) {
if (preg_match('/^[a-f0-9]+$/i', $data)) {
return true;
}
}
return false;
}
That's an untested function. It looks right though.

BostonGuru
08-13-2006, 06:53 PM
Can the characters "/" or "\" potentially cause problems?

brendandonhu
08-13-2006, 07:31 PM
If you're trying to prevent SQL injection, use the quote_smart() function from http://us2.php.net/mysql_real_escape_string

$howdy$
08-14-2006, 05:08 PM
Don't take any offense to this bro, but I'm going to quote your statement to prove a point.


I'm going to point out why this statement is not 100% accurate:

If you're trying to prevent SQL injection, use the quote_smart() function from http://us2.php.net/mysql_real_escape_string


Here's how I made that statement 100% accurate: (pay attention to the bold word)

If you're trying to prevent MySQL injection, use the quote_smart() function from http://us2.php.net/mysql_real_escape_string


mysql_real_escape_string() function works only if you are running MySQL

This might be shocking for some, but there are actually people that do not use use MySQL as a database engine.

Let me also point out again that when using mysql_real_escape_string(), a database connection must have already been established. A quick sarcastic response to this statement would an easy duh!. But consider this point of view of mine:

When I code, I like to do a lot of checks. I have set up everything to show all errors. However, I die() all the Warnings and Notices...I don't give them a break and stop the whole script. Well what the hell does that have to do with anything, you ask? Well, I connect to the database at the end. If I have to exit a PHP script during one of my checks, why should I bother putting an extra overhead with a database connection if the error generated has nothing to do with one of my database sql checks. Well that's the way I see it.

brendandonhu
08-14-2006, 07:31 PM
This might be shocking for some, but there are actually people that do not use use MySQL as a database engine.
Yeah, hopefully most people figured out that its for mysql, since the function is called mysql_real_escape_string. Otherwise they might want pg_escape_string or dbx_escape_string.