Important alert: (current site time 9/2/2014 9:46:32 PM EDT)
 

article

PHP and Security - Tutorial

Email
Submitted on: 12/11/2001 6:57:39 AM
By: Richard Silvers  
Level: Intermediate
User Rating: By 66 Users
Compatibility: PHP 3.0, PHP 4.0
Views: 58266
(About the author)
 
     Hopefully aiding in writing more secure PHP scripts.

 
 
Terms of Agreement:   
By using this article, you agree to the following terms...   
  1. You may use this article in your own programs (and may compile it into a program and distribute it in compiled format for languages that allow it) freely and with no charge.
  2. You MAY NOT redistribute this article (for example to a web site) without written permission from the original author. Failure to do so is a violation of copyright laws.   
  3. You may link to this article from another website, but ONLY if it is not wrapped in a frame. 
  4. You will abide by any additional copyright restrictions which the author may have placed in the article or article's description.
				

This is my first contribution in the form of a PSC tutorial, so please bear with me. In this tutorial I am going to discuss security as it relates to protecting your PHP scripts from preying eyes, as well as protecting your system and your web pages from would-be assailants. This tutorial walks through some exploits regarding PHP, as well as fixes for them. This article, this site, or I do not condone using any of this knowledge in a devious or malicious manner.


The rest of this tutorial will be laid out in the following sections:



1. PHP and Security

2. Site Defacement

3. Externally working with variables

4. File Access

5. Encryption

6. Cookie Encryption

7. Protecting Scripts

8. One-way Password Authentication

Section 1: PHP and Security


While most of the world focuses on crackers gaining access to websites from bugs in the web server amongst other things, the major flaws that simply running PHP on your system can cause go unnoticed. Most new PHP users, and probably some veteran ones, don't fully understand the holes that can be readily opened by a single ill-written PHP script. You may think opening a local file on your system is secure, but is it? We'll explore this and other exploits later in this tutorial.

PHP is a very forgiving language, and with this (if not because of it), it is very easy to design PHP programs that have bugs or undesirable consequences. Bugs are easier to implement in PHP programs mainly because of the way it handles variables. Not only can different types of variables be loosely assigned to one another, but also PHP doesn’t really care where a variable has come from, which leads to large security holes.

Section 2: Site Defacement


Site defacement is a common occurrence on the Internet. Site defacement is where a person, by various means, is able to edit the HTML code of a web page and use it to display their own content. This attack is usually more embarrassing than it is harmful, but this is not always the case. A person with access to the source of a web page can do obvious things like link to URLs containing malicious software or not-so-obvious things such as display false statements on a company’s website, thus embarrassing the organization and possibly ruining its reputation.

Site defacement is just one of the many threats web server administrators have to deal with every day that they run their site, but it is also one of the most common. You may be surprised to learn just how easy it is to set yourself up for site defacement in PHP.

PHP is commonly used to develop guest books, web bulletin boards, or just about anything else that requires some form of input from users. This is where most people get into trouble. Examine the following piece of hypothetical code:

<?
print(“$MsgAuthor[27]
”);
print(“$MsgDate[27]
”);
print(“$MsgSubject[27]
”);
print(“$MsgBody[27]
”);
?>

This code, taken from a bulletin board, sends the Author name, Date, Subject, and Body of the 27th entry to anyone that clicks on the link to it. This code will function perfectly; in fact, it will do exactly what you want it to. But what you want is probably not what is best. Suppose that when the person who originally created the 27th entry he put some HTML code of his own in $MsgBody. What will happen to the next person who reads that message? Basically, whatever the creator wanted. It could be anything from displaying harmless images to malicious things such as repetitive loading of java script pop-up windows.

There are instances where you would users to be able to enter HTML into their message bodies, but I don’t. (And since I am writing this tutorial, you have to go along). In case you don’t want to allow this either, PHP provides you with a function that will take care of this problem for you. Let’s take a look at it:

<?
print(“$MsgAuthor[27]
”);
print(“$MsgDate[27]
”);
print(“$MsgSubject[27]
”);
print(htmlspecialchars($MsgBody[27]) . “
”);
?>

The htmlspecialchars() function strips all of the HTML parsing symbols (<, >, &) and replaces them with their equivalents as html entities (< > &). This prevents the execution of any HTML you don’t want.

In addition to the htmlspecialchars() function there is also a htmlentities() function that will strip out all special characters and replace them with their respective HTML entity equivalents.

This does not fix all possibilities for site defacement, but it fixes a major one within PHP that not many people seem to know exist.

Section 3: Externally Working With Variables


This is a large issue with PHP and can have an even larger number of results depending on what the script does. This flaw relies on the fact that variables in PHP do not need to be defined, and can be created directly from the URL. Examine the following piece of code:

<?
If ($Password == “root”) {
$AdminPass = TRUE;
}

If ($AdminPass == TRUE) {
print(“Welcome to the system. Enjoy yourself.”);
} else {
print(“Password refused. Try again.”);
return;
}

?>

What’s wrong with this script? Besides the fact that it is poorly written, nothing is wrong with it, right? PHP will still interpret and execute this script. If we were to hypothetically assume that $AdminPass contains whether the administrator password was accepted or not, it appears fine. Now lets pretend that this script is entitled “admin.php” and was called from the URL: http://www.thesite.com/admin.php?AdminPass=1. What happens? You can bypass the test and access the site without even having to enter a password.

Now remind yourself that this is a purely hypothetical situation and that hopefully no one would write such a script. But the fact that variables can be set from the URL string is quite dangerous and can have completely unexpected and unintended results that your program isn’t prepared to handle.

In the above example, there isn’t a good way to check whether or not the variable is set with the URL string or not, but by using explicit type checking you can usually predict if it is a variable you had defined or came from an external source. A good method to try is the “===” operator which checks if the arguments are equal to each other and are of the same type. The “===” operator makes sure that the values are the same and that PHP identifies them as the same type. This prevents expressions like “TRUE == 1” from returning a TRUE value.

What if the $AdminPass was set to TRUE in the URL string though? It would match even with specific type checking. The best solution to the above problem is to initialize all of the used variables in our PHP script to a null value.

<?
$AdminPass = FALSE;

If ($Password == “root”) {
$AdminPass = TRUE;
}

If ($AdminPass == TRUE) {
print(“Welcome to the system. Enjoy yourself.”);
} else {
print(“Password refused. Try again.”);
return;
}
?>

In the above example $AdminPass was initialized to FALSE as no operations have yet to be performed on it and we know that changing its value at the beginning of our program will not affect the results of the script. If there was a URL string variable supplied as $AdminPass, it will be cleared and set to FALSE, fixing a large hole.

Section 4: File Access


There are a number of holes in PHP when it is used to access files. One of the most devious is using the external variable method described above in conjunction with a script that supposedly opens “safe” files.



In browsing the World Wide Web you have undoubtedly come across URLs that are formed like this: www.thesite.com/loadpicture.php?file=butterfly.jpg. It’s obviously a script that is used to load pictures, and it is calling the file “butterfly.jpg” to load. What’s so wrong with this? Nothing as long as you know exactly what files this script can call. What if some malicious person were to edit that URL and replace it with “file=/etc/passwd”? If you are unlucky enough to allow access to /etc/passwd your password file has now been sent out over the Internet. Good work.

The unfortunate solution to this hole is that there isn’t really a good one. Included with PHP is a configuration known as open_basedir which allows you to specify which directories your PHP scripts can access, but if you intend to work with password files or any sensitive data you must be able to access it to use it, and therefore the file must be within one of the specified directories. Note that open_basedir is still a valid option to protect parts of your system which you have no need to access. For the rest of your system, however, we must use encryption.

Section 5: Encryption


Encryption. It’s the buzzword of the Internet recently. If you aren’t encrypting all of your web traffic, e-mail messages, telnet sessions, and about everything else you use online, you just aren’t cool anymore. Seriously though, encryption is a highly valuable tool, especially if you are dealing with important things like password files.

Encryption is the process of taking a file, usually referred to as plaintext, and encrypting that file into an unrecognizable form known as ciphertext. Decrypting the file backwards from cipher to plaintext usually requires a key of some sort. Without the key it is very difficult to reconstruct the original plaintext message.

There are two main categories of encryption: one-way and two-way. A two-way encryption scheme will allow the encryption and decryption of text and usually requires some form of key. A one-way encryption scheme will only encrypt data and, having no key to decrypt it, will remain that way. One-way encryption is typically used for password files because it is more secure to simply encrypt attempted passwords and compare the encrypted version against encrypted passwords already inside the password file.

PHP will often gracefully handle this task for you, but you must remember that if you use a two-way password encryption scheme, the encrypted file is only as safe as the access to your decryption routines. A better way to handle encryption, and one that is commonly used to encrypt the /etc/passwd file on Unix, is one-way encryption. PHP allows for this form of encryption in it’s crypt() function.

Section 6: Cookie Encryption


The biggest proponent of cookies is commercial websites, though this isn’t always the case. Whether you are using a cookie to keep track of someone’s buying habits, or just to more specifically tailor your site to fit their personal needs, your cookies should always be encrypted.

If you do not encrypt your cookies they are viewable as plain text by the user and thus, he or she can then easily modify the cookie’s data. Not only can this have bad results for your site, as PHP automatically loads cookies into variables (and thus their modifications), but they could LIE to you about their buying habits or their household income, now isn’t that terrible?

Seriously now, the main flaw is that the modified cookies are automatically loaded into PHP and have basically the same effect as creating variables from the URL string (shown in Section 3). If you don’t encrypt your cookies your PHP program must be set up to distinguish variables that you set from variables that the user may set by modifying their cookies, it’s not an easy task.

Cookie encryption is as easy as:

<?
setcookie(base64_encode($cookie));
?>

That’s it. Using the built-in base64_encode() function, the cookie will be encrypted before being sent to the user. It’s such an easy hole to fix; it’s amazing that not more people do. Using just the base64_encode() function should provide sufficient security, but if someone were to know what method you had used to encrypt it, decrypting it would be as easy as loading the cookie into PHP and running base64_decode(). If you handle highly sensitive data with cookies and want better security, there are other encryption functions you can use that are supplied by PHP.

Section 7: Protecting Scripts


Unfortunately there isn’t a lot you can do to protect your script from people who have physical access to your system. The scripts must be readable or else the web server will not be able to load them, meaning that anyone who has security clearance at or above the level of the web server can potentially access your scripts. This is rather dangerous because anyone who has access to your scripts can see how you have encrypted your scripts, see where you store the passwords, modify the scripts content, and do basically whatever they want to them. The good news to this, however, are that the chances of your script being released by the web server are slim, and doubly so if you have installed PHP as a module.

Section 8: One-way Password Authentication


When a script works with passwords unfortunately it is common to see some of them work like this (Note that the decrypt() function is not a PHP function, but merely a hypothetical representation of some user-created functions):



<?
$userpass = decrypt($userpass);
if ($attemptedpass == $userpass) {
print(“Welcome to the system.”);
} else {
print(“Wrong. Try again.”);
}
?>

The problem with the script is that you decrypt the password at all. The decrypted version of the users password is stored in memory, even if only temporarily, and is being passed around inside your script. It is not safe. A much safer version of this is to use one-way encryption to compare the passwords.



Compare the above script with this one:

<?
If(crypt($attemptedpass) == $userpass) {
print(“Welcome to the system.”);
} else {
print(“Wrong. Try again.”);
}
?>

This script is much safer. The attempted password is encrypted, and if it is the same as the $userpass, the encrypted versions of both will match. This makes sure that no unencrypted passwords are being passed around throughout your PHP script.

---

That’s the end of the tutorial. If you had the stomach to read this much of my writing, and liked it, vote for it. :)

-Richard J. Silvers


Other 5 submission(s) by this author

 


Report Bad Submission
Use this form to tell us if this entry should be deleted (i.e contains no code, is a virus, etc.).
This submission should be removed because:

Your Vote

What do you think of this article (in the Intermediate category)?
(The article with your highest vote will win this month's coding contest!)
Excellent  Good  Average  Below Average  Poor (See voting log ...)
 

Other User Comments

12/12/2001 6:02:24 AMDavid

Nice job Richard. I can tell you put some time and thought into this. In my experience, security is an issue that primarily deals with logic, common sense, and awareness. You seem to have expressed that. Nice article.
(If this comment was disrespectful, please report it.)

 
12/13/2001 9:52:50 PMfrikk

very good tutorial, i noted a vew pointers. what would you suggest for a basic coding, besides base64? (i already use that for cookies).
(If this comment was disrespectful, please report it.)

 
12/23/2001 12:44:44 PMVlad

You forgot to mention another significant security hole. When a user submits a form, the programmer can choose to pass some
(If this comment was disrespectful, please report it.)

 
3/31/2002 4:06:40 PMDarkHarmer

Very nicely laid out. I enjoyed the part about encypting cookies. At least now I know how to protect myself from these "Holes" in the PHP language. Thanks for sharing this piece of art with us.
(If this comment was disrespectful, please report it.)

 
4/4/2002 4:28:12 AMsabre

thats great...
never realised some of that. real useful
thx
(If this comment was disrespectful, please report it.)

 
1/14/2003 1:17:00 PM

for the file open problem:

image.php?theimage=/bug.jpg

first specify a base directory for your images
relative to your php file

$base = "images/incests";

then use the fallowing for security checking

$theimage = strstr($theimage,"/");

you get alot of security experience from bordom

if this is messed up who cares wooo!
(If this comment was disrespectful, please report it.)

 
4/2/2003 4:03:47 PM

Another signifiant security issue: the GET variables (from query string). They can be easily modified causing unexpected execution of your PHP script. As the latest PHP distributions suggest, take care to not register gloabal variables.
(If this comment was disrespectful, please report it.)

 
7/9/2003 11:06:40 PMRichard Silvers

Ehh. Maybe you don't know how to read or something. I -said- it was insecure. That is why I used it as an example of insecurity. In fact, what I said was: Now lets pretend that this script is entitled "admin.php" and was called from the URL: http://www.thesite.com/admin.php?AdminPass=1. What happens? You can bypass the test and access the site without even having to enter a password.

So. Thanks, captain obvious.
(If this comment was disrespectful, please report it.)

 
7/18/2003 2:59:49 AMFuRiOuS1

LOL, some ppl, oh yeah, great code, keep up the good work.
P.S.
An updated version of this that tells alot more would be great!
(If this comment was disrespectful, please report it.)

 
1/14/2004 12:58:48 AM

I am using VB 6.0 and Access database i want when operator login then Some Button Enable and when administrator login All buttons will be Disable.
(If this comment was disrespectful, please report it.)

 
1/14/2004 1:03:13 AM

i am using Vb6.0 and access as a database i want code of login, when operator login Some Button Hide(Enable)and when admin login All Button Unhide(Disable).I Use Combobox for username.
(If this comment was disrespectful, please report it.)

 
2/7/2004 3:48:57 AM

An excellent article Richard; clearly and concisely conveyed. Please write more!
(If this comment was disrespectful, please report it.)

 
5/12/2004 1:35:38 PM

very nice work man
(If this comment was disrespectful, please report it.)

 
6/26/2004 11:48:40 PMDario S.

Great work. This is a very detailed tutorial that anyone using php scripts on websites should read.

(If this comment was disrespectful, please report it.)

 
7/1/2004 4:19:23 PM

Thanks...salam indonesia....
(If this comment was disrespectful, please report it.)

 
10/28/2004 3:20:25 PM

is it against copyright to use this tutorial on my site and give you FULL credit and give a link to this page?
(If this comment was disrespectful, please report it.)

 
10/28/2004 3:21:03 PM

hi please email me with response (majdaa@gmail.com)
(If this comment was disrespectful, please report it.)

 
12/5/2004 5:14:08 AM

I like this site
(If this comment was disrespectful, please report it.)

 
12/19/2004 11:18:17 AMPeter van Velzen

Solution for problem #3:

if($_REQUEST['AdminPass']){
print 'Hack attempt, terminating script';
exit();
}
(If this comment was disrespectful, please report it.)

 
12/19/2004 3:34:28 PMRichard Silvers

Which only works with PHP 4.1 or higher. The article is a little bit dated. :)
(If this comment was disrespectful, please report it.)

 
1/4/2005 6:40:09 AM

i m doing work in php for an orgonisation.i would not want to give piece of source-code to that orgonization.i want to give them encrypted source-code but when this runs page decrypt it and parse php code to webserver. how can i do this..
any hint will be appriciated

Khurram
(If this comment was disrespectful, please report it.)

 
1/24/2005 11:25:25 AM

It is a good tutorial but who in the world would send a password in the URL!!
Use $HTTP_POST_VARS for variables sent by a submitted form and $HTTP_GET_VARS
for URL variables, you cannot leave your variables loose, i.e
$AdminPass = TRUE;
use before that
$AdminPass=$HTTP_POST_VARS['AdminPass'];
I hope that I did not misunderstand you.
You said in Section 3: [There isn’t a good way to check whether or not the variable is set with the URL string or not]
(If this comment was disrespectful, please report it.)

 
1/10/2006 5:26:25 PMLawrence Cherone

nice tutorial, reading the comments you recived was very enlighting too.. nice one all.
(If this comment was disrespectful, please report it.)

 
8/22/2006 10:12:24 PMJeff

great stuff, good read.
(If this comment was disrespectful, please report it.)

 
2/14/2007 1:42:54 AMdan

thanks alot for this guy...
this very usefull for me right now...
(If this comment was disrespectful, please report it.)

 
11/29/2007 11:11:12 AMOrakio

2: You should in most cases filter the HTML when being inputed. There are few occasions where this might be bad and does need checks (size increase).

3: The simple solution is to turn off the setting that does this. Then use the associative arrays.

4: The user should never have to specify the directory, a simple regex blocking all \ or / separators solves your problems. There are very few cases where it is necessary not to do this and most of those only need it to put a link in the page such as for a back link rather than produce the output of a file.

6: Turn off the mapping global variables setting. Don't put sensitive data in these, store everything server side. All you need is a long session key. Never use base64. Use something that needs a >= 32bit key or make something. A simple encryption you can make is to have for a 64bit key, seed rand with it, then for each byte of text to encrypt xor it with the next byte out of rand, use base64 but with your own character table.
(If this comment was disrespectful, please report it.)

 

Add Your Feedback
Your feedback will be posted below and an email sent to the author. Please remember that the author was kind enough to share this with you, so any criticisms must be stated politely, or they will be deleted. (For feedback not related to this particular article, please click here instead.)
 

To post feedback, first please login.