
|
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.
|