TalkPHP
 
 
Account Login
Latest Articles
» The basic usage of PHPTAL, a XML/XHTML template library for PHP
» Vulnerable methods and the areas they are commonly trusted in.
» Simple way to protect a form from bot
» The Basics On: How Session Stealing Works
» How to keep your forms from double posting data
IRC Channel
IRC Speech Bubble Join the friendly bunch on IRC...
(#TalkPHP on Freenode)

...Also available via a web interface.

See this thread for information on the TalkPHP Free Hugs Initiative™. Subject to availability.
Associates
Associates
CSS Tutorials
Reply
 
LinkBack Thread Tools Search this Thread Display Modes
Old 09-06-2007, 04:26 PM   #1 (permalink)
Wizard
Top Contributor 
 
Village Idiot's Avatar
 
Join Date: Sep 2007
Posts: 1,299
Thanks: 17
Village Idiot is on a distinguished road
Default Tips: PHP security

Making Secure PHP Applications

There are 2 basic types of attacks that a cracker will try to gain access you don't really want him to have. This lesson runs though what the cracker does and how you can fight against. This is really anything but a definitive guide to security, there is no possible way to cover security in a 4 page article. But this is a good start to security. To conclude the introduction, XSS attacks are too broad of a range and wont be covered here.

Basics
Generally, scripts that don't have their source revealed to the public are harder to crack. Scripts which you can get the source from always have to take more precautions. Either way, the same precautions should be taken, not giving the source doesn't make it uncrackable. Security is extremely important, I learned that when I had a bug in img911's script that allowed php files to be uploaded. There was a script that gave the uploader full control of my files, the site was only a week old! People will come to your site and try to gain access if they can. It is a matter of time before it happens to you, are you ready?

Attack One: SQL Injection Attacks
What it is
This is by a good margin the most common type of attack because of its shear power, that and its easy to do. SQL injection attacks inject commands via user imputed data that could cause damage to your database.

How it works
SQL injection attacks happen when you modify data that is being sent into a database, for instance
showimage.php?id=1
You could change that to
showimage.php?id=1'; DELETE FROM images WHERE 1;
That would create an error for the sql if there is any command passed the WHERE clause, but the DELETE command would run and work. This gives the cracker full control of your database, anything you could do with mysql_query, he can do. He doesnt just have to use get data, he can use POST data that is being sent from a form, he can also edit cookies that the site uses.


How to prevent
There are a few ways to prevent an SQL attack.

Method 1, Clean the data
The first way is to strip slashes, quotes and other things that have no legit purpose in the query. THIS IS NECESSARY IN ANYTHING THAT IS USER INPUTED AND WILL BE USED WITH A DATABASE! User inputted data is anything that can be edited from the outside, GET data (.php?getdata=data), user inputted POST data and cookies can all be edited by a user. Anything coming from those must be cleaned or your script is not safe. I use this function to clean my data
PHP Code:
function sql_safe($value
{
    
// Stripslashes
    
if (get_magic_quotes_gpc()) 
    {
        
$value stripslashes($value);
    }

    
// Quote if not integer
    
if (!is_numeric($value) || $value[0] == '0')
    {
        
$value mysql_real_escape_string($value);
    }
    return 
$value;

Just make all your user inputed data data like this
PHP Code:
$var sql_safe($_GET[“data”]); 
This way, all invalid data is stripped out and your database is safe. This method is not an option, every application you make must have this or equivalent code, this is the only sure fire method that cant be hacked. If you do this with all your data, your site is safe from SQL injection attacks. The next two methods are icing on the cake, not hacker proof methods.

Method 2, Table prefixes
Fact of life is everyone makes mistakes, chances are pretty high you will forget to clean one user inputted data down the road, as small as the chance is, a cracker might find it. Most programs use standard names for their database, such as in a forum the table that holds the posts would be named “posts”. Crackers know this and will try every relevant name to the type of site it is. The best way to get around this is to add a prefix to your table name, instead of “posts”, make it “forum_posts”, even a common prefix like that makes it a good deal harder to hack. I use the first 3 letters of my control panel login name as my db prefix. Do not rely on this method, it just makes it harder for a cracker to get in should you miss a step.


Method 3, Don't give sql user delete rights
This method is anything but a strict guideline, most of the scripts I make require deleting rows. But if it isn't needed and you don't have to delete rows, don't give the user permission to. This will make it so that if you forgot to clean and he got the prefix, he can't delete anything. Use it when you can, but don't change a script to cater to this, it is little more then a final precaution.



Attack 2, forged data

What it is
Forged data is when you edit a cookie to make yourself look like an admin. The only way to let this happen is flawed design, generally static data or not confirming the data is question. As rare as it may seem, it is an error Ive seen allot, even in some scripts that are for sale. This one is more obscure, but it can happen if you encounter the wrong person. This can be worse then sql injection attacks because it is less apparent; you don't have to destroy anything. Lastly, you normally have to have access to the script to do this, but not always.

How its done
Cookies can be edited easily, they are just text files on your computer. A cracker will go in and change the data to imitate what the script thinks is an admin. In firefox, to view the cookie all you have to do it go to tools -> options -> privacy -> view cookies. To change them you have to shut the browser off and go to C:\Documents and Settings\name\Application Data\Mozilla\Firefox\Profiles\profile name\cookies.txt

All your cookies reside in there, it is not encrypted so you can change anything you want to.


How to protect it

A lesson in how to make secure login cookies
What many scripts do is lay cookies like this
cookie 1:
username

cookie 2:
pass

cookie 3:
rank

This makes it easy to see if they are an admin or not, but there is one huge problem, all you have to do is change the rank cookie to what it looks for with an admin and you are in. This is a flawed design, what you have to do is this

cookie 1:
user ID

cookie 2:
user pass, encrypted via sha1 (you use sha1 when storing it in the db, right?)

When it sees a cookie, it will go into the database and see if the user with that id has that password. If it does, the user is who it says it is, you can see their rank in the database. If the data doesn't match, you delete the cookie and they are off. The only way to hack this is to know the pass, and if the cracker knew that he wouldn't be going in the back door.

Hold as little data as you can in the cookie, all it needs to do is provide solid data that the user in question is legit, not what rank they are.

I hope this article will help you make more secure PHP applications, if their is anything that has slipped my mind for this, please drop me a pm.


License
Feel free to post this anywhere as long as the below line is here
Originally written by Village Idiot
Village Idiot is offline  
Reply With Quote
The Following User Says Thank You to Village Idiot For This Useful Post:
Brook (12-14-2007)
Old 09-06-2007, 04:30 PM   #2 (permalink)
The Frequenter
Prolific Welcomer Upcoming Programmer 
 
Join Date: Sep 2007
Posts: 360
Thanks: 24
Haris is on a distinguished road
Default

Thanks Ken. :)
Haris is offline  
Reply With Quote
Old 09-06-2007, 04:46 PM   #3 (permalink)
The Reckoner
Advanced Programmer Top Contributor 
 
Karl's Avatar
 
Join Date: Sep 2007
Posts: 437
Thanks: 22
Karl is on a distinguished road
Default

Thanks for posting Village Idiot, that's a really nice introduction to PHP security. I hope you don't mind, but here is my contribution:

Using sessions can be an alternative to using cookies. You can store common data, such as username, userid, email, etc. in a session and then store the session id along with the cookie. Then when the user logs in, use the cookie session id to retrieve the session data.

There are some precautions to using this method, for example a user could guess an administrators session id, thus introducing a new concept, session hijacking.

To combat this the developer could store a unique key along with the session id. This unique key would be stored in both the users cookie and in the session. Then when a user requests the session, compare the unique key in the session to the one stored in their cookie, if they don't match, don't allow them access to the session and destroy their cookie.

You can improve the security further by regenerating session ids on every request, but I'll leave that for another day :)
Karl is offline  
Reply With Quote
Old 09-06-2007, 04:56 PM   #4 (permalink)
La Vida es Sueño
Advanced Programmer Top Contributor 
 
Wildhoney's Avatar
 
Join Date: Sep 2007
Location: Oldham
Posts: 2,280
Thanks: 90
Wildhoney is on a distinguished road
Default

There is another potential security downfall to sessions called session fixation that every developer should be consciously aware of. This is the process by which I send you a link, such as:

You click the link and login to your account. Now, if the session ID is not changed when the user logs in using session_regenerate_id then there's no need for me to guess your session ID. I already know it because I supplied it to you. PHP creates the session ID on-the-fly if it does not exist so the session ID I sent you in the link becomes your session ID. I can then go and click on the link myself and have almost limitless access to your account.

For this reason, the session's ID should be regenerated, and if practicable, the session ID within the GET should be disabled.

Please see the attached document I have outlining session fixation. It's an interesting read.
Attached Files
File Type: pdf session_fixation.pdf (404.6 KB, 715 views)
__________________
The man who comes back through the Door in the Wall will never be quite the same as the man who went out.
Send a message via AIM to Wildhoney Send a message via MSN to Wildhoney Send a message via Yahoo to Wildhoney
Wildhoney is offline  
Reply With Quote
Old 09-06-2007, 08:03 PM   #5 (permalink)
The Contributor
 
localhost's Avatar
 
Join Date: Apr 2007
Location: Hampshire
Posts: 28
Thanks: 1
localhost is on a distinguished road
Default

Thanks for the article Ken and then thankyou Wildhoney. I did know everything here (except wild's thing) but it will help many other people.. :)
__________________
Send a message via MSN to localhost Send a message via Skype™ to localhost
localhost is offline  
Reply With Quote
Old 09-13-2007, 09:08 AM   #6 (permalink)
The Wanderer
 
Join Date: Sep 2007
Location: Sydney, Australia
Posts: 19
Thanks: 0
jordie is on a distinguished road
Default

Another note (possibly for use in the safe_sql() function above, or at least in addition to), when using get/post data in a query. If you're expecting a number, e.g. a table primary index ID, then you should typecast it to make sure its a number. e.g.

myscript.php?action=save&id=34

PHP Code:
$id = (int)$_GET['id'];
mysql_query("update ... where id='".$id."'"); 
This will mean that if $_GET['id'] is anything other than a number, it will be set to 0, and as mysql starts row numbers from 1, nothing will get updated and you're safe from an injection too.
jordie is offline  
Reply With Quote
Old 09-13-2007, 02:10 PM   #7 (permalink)
Wizard
Top Contributor 
 
Village Idiot's Avatar
 
Join Date: Sep 2007
Posts: 1,299
Thanks: 17
Village Idiot is on a distinguished road
Default

Quote:
Originally Posted by jordie View Post
Another note (possibly for use in the safe_sql() function above, or at least in addition to), when using get/post data in a query. If you're expecting a number, e.g. a table primary index ID, then you should typecast it to make sure its a number. e.g.

myscript.php?action=save&id=34

PHP Code:
$id = (int)$_GET['id'];
mysql_query("update ... where id='".$id."'"); 
This will mean that if $_GET['id'] is anything other than a number, it will be set to 0, and as mysql starts row numbers from 1, nothing will get updated and you're safe from an injection too.
It just wouldn't return anything, no injection would result. If you want to see if its a number, there is the is_numeric() function
Village Idiot is offline  
Reply With Quote
Old 09-13-2007, 02:47 PM   #8 (permalink)
La Vida es Sueño
Advanced Programmer Top Contributor 
 
Wildhoney's Avatar
 
Join Date: Sep 2007
Location: Oldham
Posts: 2,280
Thanks: 90
Wildhoney is on a distinguished road
Default

Are you sure about that Village Idiot? I'm fairly sure if the (int) typecasting is unable to wither the string down to an integer, it will leave you with zero (0). In addition to Jordie's comment, which is spot on, you may also use sprintf as well. Like so:

PHP Code:
$szSQL sprintf("update ... where id=%d"$id); 
__________________
The man who comes back through the Door in the Wall will never be quite the same as the man who went out.
Send a message via AIM to Wildhoney Send a message via MSN to Wildhoney Send a message via Yahoo to Wildhoney
Wildhoney is offline  
Reply With Quote
Old 09-13-2007, 03:04 PM   #9 (permalink)
Wizard
Top Contributor 
 
Village Idiot's Avatar
 
Join Date: Sep 2007
Posts: 1,299
Thanks: 17
Village Idiot is on a distinguished road
Default

Quote:
Originally Posted by Wildhoney View Post
Are you sure about that Village Idiot? I'm fairly sure if the (int) typecasting is unable to wither the string down to an integer, it will leave you with zero (0). In addition to Jordie's comment, which is spot on, you may also use sprintf as well. Like so:

PHP Code:
$szSQL sprintf("update ... where id=%d"$id); 

It returns zero rows because PHP doesn't look if its an int or a varcher (ect) that it is looking for, not does SQL.
Village Idiot is offline  
Reply With Quote
Old 09-13-2007, 03:07 PM   #10 (permalink)
La Vida es Sueño
Advanced Programmer Top Contributor 
 
Wildhoney's Avatar
 
Join Date: Sep 2007
Location: Oldham
Posts: 2,280
Thanks: 90
Wildhoney is on a distinguished road
Default

Quote:
Originally Posted by Village Idiot View Post
It returns zero rows because PHP doesn't look if its an int or a varcher (ect) that it is looking for, not does SQL.
Sorry, I don't understand. Please explain.
__________________
The man who comes back through the Door in the Wall will never be quite the same as the man who went out.
Send a message via AIM to Wildhoney Send a message via MSN to Wildhoney Send a message via Yahoo to Wildhoney
Wildhoney is offline  
Reply With Quote
Old 09-13-2007, 03:16 PM   #11 (permalink)
Wizard
Top Contributor 
 
Village Idiot's Avatar
 
Join Date: Sep 2007
Posts: 1,299
Thanks: 17
Village Idiot is on a distinguished road
Default

I replied to jordies post saying that you dont need to typecast to int because you can just use is_numeric, either way looking for a string when its an int will just return zero rows.
Village Idiot is offline  
Reply With Quote
Old 09-28-2007, 07:04 PM   #12 (permalink)
La Vida es Sueño
Advanced Programmer Top Contributor 
 
Wildhoney's Avatar
 
Join Date: Sep 2007
Location: Oldham
Posts: 2,280
Thanks: 90
Wildhoney is on a distinguished road
Default

Oh I get where you're coming from now. Strictly speaking though if you're expecting an integer, you wouldn't place quotes around it. So if it's allowed to become a string then it would result in a MySQL error.
__________________
The man who comes back through the Door in the Wall will never be quite the same as the man who went out.
Send a message via AIM to Wildhoney Send a message via MSN to Wildhoney Send a message via Yahoo to Wildhoney
Wildhoney is offline  
Reply With Quote
Old 09-29-2007, 03:02 AM   #13 (permalink)
The Acquainted
Upcoming Programmer 
 
CMellor's Avatar
 
Join Date: Sep 2007
Location: Leeds, UK
Posts: 141
Thanks: 6
CMellor is on a distinguished road
Default

I think I'm gonna use that function you have in method 1 on my project. Currently I use:

PHP Code:
<?php
function escape($str) {
    return 
htmlspecialchars(mysql_real_escape_string($str));
}
?>
Your's seems safer...
__________________
Not quite a n00b...
CMellor is offline  
Reply With Quote
Old 09-29-2007, 03:13 AM   #14 (permalink)
The Wanderer
 
Join Date: Sep 2007
Location: Sydney, Australia
Posts: 19
Thanks: 0
jordie is on a distinguished road
Default

Quote:
Originally Posted by CMellor View Post
I think I'm gonna use that function you have in method 1 on my project. Currently I use:

PHP Code:
<?php
function escape($str) {
    return 
htmlspecialchars(mysql_real_escape_string($str));
}
?>
Your's seems safer...
It seems OK, only problem is with the output you'll need to run html_entity_decode if you've got html in there you need to work in the browser.

In regards to my earlier post, yeah I didn't mean to put the quotes in. Also, using the typecast means you can check it before and, saving a query:

PHP Code:
$id = (int)$_GET['id'];
if(
$id 0){
   
// query
}else{
   echo 
"You're not what I expected!";

jordie is offline  
Reply With Quote
Old 09-29-2007, 03:32 AM   #15 (permalink)
The Wanderer
 
Join Date: Sep 2007
Location: Sydney, Australia
Posts: 19
Thanks: 0
jordie is on a distinguished road
Default

Oh and seeing as this seems to be generic php security thread, I'd like to add: Attack Three: Mail injection

I found this other thread by William that talks about mail() in PHP. Its fairly good and gets to the point. There is a problem with security if you rely on data being input from a user.

For example, if you have an "email a friend" on a page, you mail code might look like:

PHP Code:
$strFriendsName  $_POST['FriendsName'];
$strFriendsEmail $_POST['FriendsEmail'];
$strSenderEmail  $_POST['SenderEmail'];
$strSenderName   $_POST['SenderName'];
$intPageID       = (int)$_POST['PageID'];

// lets assume a query is run here to get the page data, returning the page name as $strPageName

$to      $strFriendsEmail;
$subject "Your friend has recommended a page";
$body    "Hi ".$strFriendsName "\n Your friend has recommended this web page titled: ' " $strPageName "\n\n You can view it at this address:\n\n http://www.example.com/pages/".$intPageID;
$from    $strSenderEmail;

mail($to$subject$body"From: $from"); 
This can be exploited by inserting new lines into the post content and adding in new headers, e.g. Cc: or Bcc:. Spammers can take advantage of this and use your script to send their spam.

e.g. Instead of the SenderEmail just being an email, the could post in:
dummyEmail@example.com\nBcc: victim1@example.com, victim2@example.com etc.

The way to ensure this doesn't happen is to make sure there are no new lines, like this:

PHP Code:
function StripNewLines($str){
    return 
str_replace(array("\r""\n"), ""$str);
}

$strFriendsName  StripNewLines($_POST['FriendsName']);
$strFriendsEmail StripNewLines($_POST['FriendsEmail']);
$strSenderEmail  StripNewLines($_POST['SenderEmail']);
$strSenderName   StripNewLines($_POST['SenderName']);
$intPageID       = (int)$_POST['PageID'];

// lets assume a query is run here to get the page data, returning the page name as $strPageName

$to      $strFriendsEmail;
$subject "Your friend has recommended a page";
$body    "Hi ".$strFriendsName "\n Your friend has recommended this web page titled: ' " $strPageName "\n\n You can view it at this address:\n\n http://www.example.com/pages/".$intPageID;
$from    $strSenderEmail;

mail($to$subject$body"From: $from"); 
It might also be advisable to create a ValidateEmailAddress() function so if the email isn't in the correct format it won't even be parsed to the mail() function.

If i missed anything here, feel free to add to it or comment. Constructive criticism is more than welcome, especially when we're dealing with security. :)
jordie is offline  
Reply With Quote
Old 09-29-2007, 03:30 PM   #16 (permalink)
Wizard
Top Contributor 
 
Village Idiot's Avatar
 
Join Date: Sep 2007
Posts: 1,299
Thanks: 17
Village Idiot is on a distinguished road
Default

Understand one thing about my coding, im all about security, it bothers me to have an insecure script. But I am also concerned about simplicity, I never use complicated code where simpler code will do. The simplest way is to use an email validation command, that way you see if its a valid email and it wont let an attack in.

PHP Code:
function  checkEmail($email) {
 if (!
preg_match("/^( [a-zA-Z0-9] )+( [a-zA-Z0-9\._-] )*@( [a-zA-Z0-9_-] )+( [a-zA-Z0-9\._-] +)+$/" $email)) {
  return 
false;
 }
 return 
true;

</span>
Village Idiot is offline  
Reply With Quote
Old 09-29-2007, 08:03 PM   #17 (permalink)
Moderateur
RegEx Guru PHP Guru Top Contributor Advanced Programmer 
 
Salathe's Avatar
 
Join Date: Apr 2007
Posts: 1,393
Thanks: 5
Salathe is on a distinguished road
Default

Quote:
Originally Posted by Village Idiot View Post
Understand one thing about my coding, im all about security, it bothers me to have an insecure script. But I am also concerned about simplicity, I never use complicated code where simpler code will do. The simplest way is to use an email validation command, that way you see if its a valid email and it wont let an attack in.

PHP Code:
function  checkEmail($email) {
 if (!
preg_match("/^( [a-zA-Z0-9] )+( [a-zA-Z0-9\._-] )*@( [a-zA-Z0-9_-] )+( [a-zA-Z0-9\._-] +)+$/" $email)) {
  return 
false;
 }
 return 
true;

</span>
Why not just go with:
PHP Code:
// Changed function name because true/false makes no sense
// with a function called "checkEmail"
function isValidEmail($szEmail) {
    static 
$szPattern '/^[a-z0-9][a-z0-9._-]*@[a-z0-9_-][a-z0-9._-]+$/i';
    return (bool) 
preg_match($szPattern$szEmail);

Is 0@-- a valid email address? (Really, I don't know for sure.)
Salathe is offline  
Reply With Quote
Old 09-30-2007, 05:27 PM   #18 (permalink)
The Wanderer
 
Join Date: Sep 2007
Location: Sydney, Australia
Posts: 19
Thanks: 0
jordie is on a distinguished road
Default

Quote:
Originally Posted by Village Idiot View Post
Understand one thing about my coding, im all about security, it bothers me to have an insecure script. But I am also concerned about simplicity, I never use complicated code where simpler code will do. The simplest way is to use an email validation command, that way you see if its a valid email and it wont let an attack in.

PHP Code:
function  checkEmail($email) {
 if (!
preg_match("/^( [a-zA-Z0-9] )+( [a-zA-Z0-9\._-] )*@( [a-zA-Z0-9_-] )+( [a-zA-Z0-9\._-] +)+$/" $email)) {
  return 
false;
 }
 return 
true;

Understandable. :) Valid addresses in the mail() function do also include: "John Smith <email@example.com>"

Also, from what I've read, injection can be put into almost any mail() parameter. So if you put user input into your subject field, you need to remove any new lines. A simple sequence of characters like "\r\n \n" can cause the subject parameter to break and allow additional headers such as Bcc to be injected. e.g. "Test\r\n \nAnother-Header: Blub" Read more @ php-security.org

So while you validate your emails (including your "to" parameter), you also need to check your subject field.

I would also change my previous function to the following after I've read a few more articles and postings.
PHP Code:
function StripNewLines($str){
    return 
str_replace(array("\r""\n","%0A","%0D"), ""$str);

jordie is offline  
Reply With Quote
Old 09-30-2007, 06:30 PM   #19 (permalink)
The Wanderer
 
Join Date: Sep 2007
Location: Sydney, Australia
Posts: 19
Thanks: 0
jordie is on a distinguished road
Default

Sorry for the double post. But I'd also like to add Attack 4: Malicious files through form uploads

We're discussing it in this thread. Summary: Check the mime type and the file extension. Use white-lists and not black-lists when doing so. And if you're uploading images you can use the getimagesize() to triple check its a valid image. :)
jordie is offline  
Reply With Quote
Old 10-03-2007, 11:24 AM   #20 (permalink)
La Vida es Sueño
Advanced Programmer Top Contributor 
 
Wildhoney's Avatar
 
Join Date: Sep 2007
Location: Oldham
Posts: 2,280
Thanks: 90
Wildhoney is on a distinguished road
Default

Spot on, Jordie. Many individuals do not even consider the possibility that an individual could place in new line and carriage returns and therefore add headers as they please. The BCC, slash, CC headers are the crucial ones as if you're allowed to enter more of those then spam has just been made a whole lot easier. I've seen an abundance of scripts vulnerable to that so people really need to think on before they go ahead and simply code. It's not as simple as all that.
__________________
The man who comes back through the Door in the Wall will never be quite the same as the man who went out.
Send a message via AIM to Wildhoney Send a message via MSN to Wildhoney Send a message via Yahoo to Wildhoney
Wildhoney is offline  
Reply With Quote
Reply



Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are On


All times are GMT. The time now is 02:25 PM.

 
     

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2013, Jelsoft Enterprises Ltd.
Search Engine Optimization by vBSEO 3.1.0
Inactive Reminders By Icora Web Design