August 17th, 2001, 09:33 AM
Security Notes (Everyone should read)
The purpose of this document is to inform PHP programmers of common security mistakes that can be overlooked in PHP scripts. While many of the following concepts may appear to be common sense, they are unfortunately not always common practice. After applying the following practices to your coding, you will be able to eliminate the vast majority of security holes that plague many scripts. Many of these security holes have been found in widely-used open source and commercial PHP scripts in the past.
The most important concept to learn from this article is that you should never trust the user to input exactly what is expected. The way most PHP scripts are compromised is by entering unexpected data to exploit security holes inadvertantly left in the script.
Always keep the following principles in mind when designing your scripts:
1. Never include, require, or otherwise open a file with a filename based on user input, without thoroughly checking it first.
Take the following example:
Since there is no validation being done on $page, a malicious user could hypothetically call your script like this (assuming register_globals is set to ON):
Therefore causing your script to include the servers /etc/passwd file. When a non PHP file is include()'d or require()'d, it's displayed as HTML/Text, not parsed as PHP code.
On many PHP installations, the include() and require() functions can include remote files. If the malicious user were to call your script like this:
He would be able to have evilscript.php output any PHP code that he or she wanted your
script to execute. Imagine if the user sent code to delete content from your database or
even send sensitive information directly to the browser.
Solution: validate the input. One method of validation would be to create a list of acceptable pages. if the input did not match any of those pages, an error could be displayed.
2. Be careful with eval()
$pages = array('index.html', 'page2.html', 'page3.html');
if( in_array($page, $pages) )
Placing user-inputted values into the eval() function can be extremely dangerous. You essentially give the malicious user the ability to execute any command he or she wishes! You may envision the input coming from a drop-down menu of options you specify, but you user may decide to send input like this:
By putting his own code in that statement the user could cause your program to output your
server's complete /etc/passwd file.
Use eval() sparingly, and by all means, validate the input. It should only be used when absolutely necessary - when there is dynamically generated PHP code. If you are using it to substitute template variables into a string or substitute user-inputted values - you are using it for the wrong reason. Try sprintf() or a template system instead.
3. Be careful when using register_globals = ON
This has been a major issue since this feature was invented. It was originally designed to make programming in PHP easier (and that it did), but misuse of it often led to security holes. As of PHP 4.2.0, register_globals is set to OFF by default. It is recommended that
you use the superglobals to deal with input( $_GET, $_POST, $_COOKIE, $_SESSION, etc).
For example, let us say that you had a variable that specified what page to include:
But you intended $page to be defined in a config file or somewhere else in the script, and not to come as user input. In one instance you forgot to pre-define $page. If register_globals is set to on, the malicious user can take over and define $page for you, by calling your script like this:
I recommend that you develop with register_globals set to OFF, and use the superglobals when accessing user input. In addition, you should always develop with full error reporting, which can be specified like this (at the top of your script):
This way, you will receive a notice for every variable you try to call that was not previously defined. Yes, PHP does not requre you to define variables so there may be notices that you can ignore, but this will help you to catch undefined variables that you did expect to come from input or other sources. In the previous example, when $page was referenced in the include() statement, PHP would issue a notice that $page was not defined.
Whether or not you want to use register_globals is up to you, but make sure you are aware of the advantages and disadvantages of it and how to remedy the possible security holes.
4. Never run unescaped queries
PHP has a feature, enabled by default, that automatically escapes (adds a backslash in front of) certain characters that come in from a GET, POST, or COOKIE. The "single quote" is one example of a character that is escaped automatically. This is done so that if you include input variables in your SQL queries, it will not treat single quotes as part of the query. Say your user entered $name from a form and you performed this query:
UPDATE users SET Name='$name' WHERE ID=1;
Normally, if they had entered $name with single quotes in them, they would be escaped, so MySQL would see this:
UPDATE users SET Name='Joe\'s' WHERE ID=1
So that the single quote entered into "Joe's" would not interfere with the query syntax.
In some situations, you may use stripslashes() on an input variable. If you put the variable into a query, make sure to use addslashes() or mysql_escape_string()to escape the single quotes before your run the query. Imagine if an unslashed query went in, and a malicious user had entered part of a query as their name!
UPDATE users SET Name='Joe',Admin='1' WHERE ID=1
On the input form, the user would have entered:
As their name, and since the single quotes were not escaped, he or she would be able to actually end the name definition, place in a comma, and set another variable called Admin!
The final query with input in blue would look like this:
UPDATE users SET Name='Joe',Admin='1' WHERE ID=1
In some configurations, magic_quotes_gpc (the feature that automatically adds slashes to all input) is actually set to OFF. You can use the function get_magic_quotes_gpc() to see if it's on or not (it returns true or false). If it returns false, simply use addslashes() to add slashes to all of the input (it is easiest if you use $_POST, $_GET, and $_COOKIE or $HTTP_POST_VARS, $HTTP_GET_VARS, and $HTTP_COOKIE_VARS, instead of globals because you could step through those arrays using a foreach() loop and add slashes to each one).
5. For protected areas, use sessions or validate the login every time
There are some cases where programmers will only use some sort of login.php script to first validate their username and password (entered through a form), test if they're an administrative or valid user, and actually set a variable through a cookie, or even hide it as a hidden variable. Then in the code, they check to see if they have access like this:
The above a code makes the fatal assumption that the $admin variable can only come from a cookie or input form that the malicious user has no control over. However, that is simply not the case. With register_globals enabled, injecting designed input into the $admin variable is as easy as calling the script like so:
// let them in
// kick them out
Furthermore, even if you use the superglobals $_COOKIE or $_POST, a malicious user can easily forge a cookie or create his own HTML form to post any information to your script.
There are two good solutions to this problem. One is on the same track as setting an $admin variable, but this time set $admin as a session variable. In this case, it is stored on the server and is much less likely to be forged. On subsequent calls to the same script, your user's previous session information will be available on the server, and you will be able to verify if the user is an administrator like so:
if( $_SESSION['admin'] )
The second solution is to only store their username and password in a cookie, and with every call to the script, validate the username and password and verify if the user is an administrator. You could have 2 functions - one called validate_login($username,$password) that verified the user's login information, and one called is_admin($username) that queried the database to see if that username is an administrator. The code would be placed at the top of any protected script:
Personally I recommend using sessions, as the latter solution is not scalable.
if( !validate_login( $_COOKIE['username'], $_COOKIE['password'] ) )
echo "Sorry, invalid login";
// the login is ok if we made it down here
if( !is_admin( $_COOKIE['username'] ) )
echo "Sorry, you do not have access to this section";
Comments on this post
August 17th, 2001, 09:35 AM
6. If you don't want the file contents to be seen, give the file a .php extension
It was common prattice for awhile to name include files or library files with a .inc extension. Here's the problem: if a malicious user simply enters the .inc file into his browser, it will be displayed as plain text, not parsed as PHP. Even if the browser did not like the file type, an option to download it would most likely be given. Imagine if this file had your database login and password, or even more sensitive information.
This goes for any other extension other than .php (and a few others), so even a .conf or a .cfg file would not be safe.
The solution is to put a .php extension on the end of it. Since your include files or config files usually just define variables and/or functions and not really output anything, if your user were to load this, for example, into their browser:
They would most likely be shown nothing at all, unless of your lib.inc.php outputs something. Either way, the file would be parsed as PHP instead of just displaying your code.
There are also some reports of people adding Apache directives that will deny access to .inc files, however I do not recommend this, because of the lack of portability. If you rely on .inc files and that Apache directive to deny access to them and one day you move your scripts to another server and forget to place the Apache directive in, you are wide open.
Comments on this post
August 17th, 2001, 10:09 AM
thats a really comprehensive information.
i think every newbie should look at it.
btw u know i follow all the things that u have mentioned but i wonder if i have been able to put it so nicely.
so thats superb jeff
now let me add something from my side.[ seems like we are following each other's posts, arent we ??!!]
anyways,as my friend said, one of the most important thing is to validate userdata. start here.
i used to do one mistake, i wonder if others do it.
in very beginning, for authentication i just used to set cookie with name logged its value being 1 or 0.
so if its 0 user is not logged. if its 1 user is logged.
so everytime i used to check cookie only. i wasnt actually checking username.
so wat one can do is, he can login , get his cookie set, and then change the username through query string !!!
and act as he is that user !! he can even log in as administrator !!!
fortunately i found this hole myself and stopped doin that.
so its great idea to store username/password both in cookie and check it everytime.
PHP in safe mode is also a good idea,though it limits ur options but it can avoid common mistakes which can be nightmare !!
hope this gives idea,
certainly other expereinced coder should write wat kind of things they take care of,
~ simple thought, simple act ~
I blog @ http://jdk.phpkid.org
August 17th, 2001, 10:56 AM
i have always wondered about security and it was a great thread. definitelly a must read. i have started to write my scripts where i put all the config files and important files in another folder and protected it with htaccess. one day php isn't going to start up properly with apache and all your files will be able to be d/led. i know it happened to devshed once. i would like to hear more ideas on this, if anyone has any more.... definitelly a must for creating php applications.
http://www.stillsucks.com and helping with
August 17th, 2001, 10:57 AM
in response to the user name and password storage in the cookie, it is a good idea to store the password (and or username) as an encrypted string, or as a md5 checksum, although this still isnt fool proof, it helps a little with security.
August 17th, 2001, 11:07 AM
i generate a lots of dynamic content and i often pass id through query string:
To avoid hacking thru that i use:I also include all code from directory, which is not accessible from web.
August 17th, 2001, 11:15 AM
Thanks Jeff. Too easy to make a botch of things security-wise. And we all need reminding of the fact.
All I want to add is a reminder of the strip_tags() function for taking the sting out of user input. Because, frankly, I don't want any code added by users, including little old html.
August 17th, 2001, 11:27 AM
Or you could also use htmlspecialchars() to escape the HTML...
August 17th, 2001, 01:40 PM
This is all about PHP programming and its security externally. Great post, right, good programming can avoid many stupid mistakes that lead to possible holes. But you failed to mention PHP as a module of Apache and its security internally. In fact, mod_php without safe-mode in a shared host environment is always not as secure as standalone PHP with suEXEC-enabled.
August 17th, 2001, 01:53 PM
I didn't fail to mention anything.
I covered what I intended to cover. Internal PHP security is another topic.
August 17th, 2001, 01:59 PM
Gad, if it's not syntax errors it's semantic errors...
Good point freebsd.
August 17th, 2001, 02:01 PM
Start here -> Apache PHP and Chroot and get some idea should help on writing up your 2nd topic.
Other than internal and external source (not PHP's fault). Your topic should be PHP itself. Every once in awhile, there is a vulnerability found in PHP source code, so all users should upgrade to the latest version.
August 17th, 2001, 02:14 PM
Ok, thanks. I am glad you brought that up and clarified, I was worried you were posting a "this topic sucks because it doesn't cover what I wanted".. kind of thing.
I'll have to look more into that, I admit I know very little (if anything) about "internal" PHP security. I don't know how exploitable it is but I did want to cover the common programming mistakes that leave your site so wide open that any 12 year old "hacker" could exploit it.
Hey - since you know more about that sort of thing than I do, maybe you could post the next topic? What do you say
August 17th, 2001, 02:21 PM
I have figured those things by myself.
I'm building a bulletin board and I have a couple of questions. First of all - Is it "safe" (I think it is) to use randomly generated session ID strings (30 characters) that expire after 10 minutes of idle time and then use functions to determine username, like this:
It's not possible to get admin priviledges like this: index.php?admins=yes or anything like that?
$username = GetUsernameFromID($session);
$admins = "no";
$userlevel = GetUserLevel(GetUsernameFromID($session));
if ($userlevel > 400) $admins = "yes";
Second question: I have this problem that the server where I'm running my scripts is a public server - many many other people use it. I have not managed to protect my PHP-files so that other users can't view them (cat /home/users/grim/public_html/index.php), so anyone can read my database passwords from the .PHP -files. Is there any way to prevent this?
Fortunately I'm a root on the server so I can do "chown www-data *php; chmod 700 *php" but if it's possible to prevent it some other way it could be useful to all of you. Any ideas?
August 18th, 2001, 02:24 AM
I would like to know under what situations do I need to use escapeshellcmd() for security purpose?
Thanks in advance,