Skip Navigation

Writing Secure PHP, Part 2

In Writing Secure PHP, I covered a few of the most common security holes in websites. It's time to move on, though, to a few more advanced techniques for securing a website. As techniques for 'breaking into' a site or crashing a site become more advanced, so must the methods used to stop those attacks.

[Writing Secure PHP is a series. Part 1, Part 3 and Part 4 are currently also available.]

File Systems

Most hosting environments are very similar, and rather predictable. Many web developers are also very predictable. It doesn't take a genius to guess that a site's includes (and most dynamic sites use an includes directory for common files) is an www.website.com/includes/. If the site owner has allowed directory listing on the server, anyone can navigate to that folder and browse files.

Imagine for a second that you have a database connection script, and you want to connect to the database from every page on your site. You might well place that in your includes folder, and call it something like connect.inc. However, this is very predictable - many people do exactly this. Worst of all, a file with the extension ".inc" is usually rendered as text and output to the browser, rather than processed as a PHP script - meaning if someone were to visit that file in a browser, they'll be given your database login information.

Placing important files in predictable places with predictable names is a recipe for disaster. Placing them outside the web root can help to lessen the risk, but is not a foolproof solution. The best way to protect your important files from vulnerabilities is to place them outside the web root, in an unusually-named folder, and to make sure that error reporting is set to off (which should make life difficult for anyone hoping to find out where your important files are kept). You should also make sure directory listing is not allowed, and that all folders have a file named "index.html" in (at least), so that nobody can ever see the contents of a folder.

Never, ever, give a file the extension ".inc". If you must have ".inc" in the extension, use the extension ".inc.php", as that will ensure the file is processed by the PHP engine (meaning that anything like a username and password is not sent to the user). Always make sure your includes folder is outside your web root, and not named something obvious. Always make sure you add a blank file named "index.html" to all folders like include or image folders - even if you deny directory listing yourself, you may one day change hosts, or someone else may alter your server configuration - if directory listing is allowed, then your index.html file will make sure the user always receives a blank page rather than the directory listing. As well, always make sure directory listing is denied on your web server (easily done with .htaccess or httpd.conf).

------

Out of sheer curiosity, shortly after writing this section of this tutorial, I decided to see how many sites I could find in a few minutes vulnerable to this type of attack. Using Google and a few obvious search phrases, I found about 30 database connection scripts, complete with usernames and passwords. A little more hunting turned up plenty more open include directories, with plenty more database connections and even FTP details. All in, it took about ten minutes to find enough information to cause serious damage to around 50 sites, without even using these vulnerabilities to see if it were possible to cause problems for other sites sharing the same server.

-----

Login Systems

Most site owners now require an online administration area or CMS (content management system), so that they can make changes to their site without needing to know how to use an FTP client. Often, these are placed in predictable locations (as covered in the last article), however placing an administration area in a hard-to-find location isn't enough to protect it.

Most CMSes allow users to change their password to anything they choose. Many users will pick an easy-to-remember word, often the name of a loved one or something similar with special significance to them. Attackers will use something called a "dictionary attack" (or "brute force attack") to break this kind of protection. A dictionary attack involves entering each word from the dictionary in turn as the password until the correct one is found.

The best way to protect against this is threefold. First, you should add a turing test to a login page. Have a randomly generated series of letters and numbers on the page that the user must enter to login. Make sure this series changes each time the user tries to login, that it is an image (rather than simple text), and that it cannot be identified by an optical character recognition script.

Second, add in a simple counter. If you detect a certain number of failed logins in a row, disable logging in to the administration area until it is reactivated by someone responsible. If you only allow each potential attacker a small number of attempts to guess a password, they will have to be very lucky indeed to gain access to the protected area. This might be inconvenient for authentic users, however is usually a price worth paying.

Finally, make sure you track IP addresses of both those users who successfully login and those who don't. If you spot repeated attempts from a single IP address to access the site, you may consider blocking access from that IP address altogether.

Database Users

One excellent way to make sure that even if you have a problem with someone accessing your database who shouldn't be able to, you can limit the damage they can cause. Modern databases like MySQL and SQL Server allow you to control what a user can and cannot do. You can give users (or not) permission to create data, edit, delete, and more using these permissions. Usually, I try and ensure that I only allow users to add and edit data.

If a site requires an item be deleted, I will usually set the front end of the site to only appear to delete the item. For example, you could have a numeric field called "item_deleted", and set it to 1 when an item is deleted. You can then use that to prevent users seeing these items. You can then purge these later if required, yourself, while not giving your users "delete" permissions for the database. If a user cannot delete or drop tables, neither can someone who finds out the user login to the database (though obviously they can still do damage).

Powerful Commands

PHP contains a variety of commands with access to the operating system of the server, and that can interact with other programs. Unless you need access to these specific commands, it is highly recommended that you disable them entirely.

For example, the eval() function allows you to treat a string as PHP code and execute it. This can be a useful tool on occasion. However, if using the eval() function on any input from the user, the user could cause all sorts of problems. You could be, without careful input validation, giving the user free reign to execute whatever commands he or she wants.

There are ways to get around this. Not using eval() is a good start. However, the php.ini file gives you a way to completely disable certain functions in PHP - "disable_functions". This directive of the php.ini file takes a comma-separated list of function names, and will completely disable these in PHP. Commonly disabled functions include ini_set(), exec(), fopen(), popen(), passthru(), readfile(), file(), shell_exec() and system().

It may be (it usually is) worth enabling safe_mode on your server. This instructs PHP to limit the use of functions and operators that can be used to cause problems. If it is possible to enable safe_mode and still have your scripts function, it is usually best to do so.

Finally, Be Completely and Utterly Paranoid

Much as I hate to bring this point up again, it still holds true (and always will). Most of the above problems can be avoided through careful input validation. Some become obvious points to address when you assume everyone is out to destroy your site. If you are prepared for the worst, you should be able to deal with anything.

Ready for more? Try Writing Secure PHP, Part 3.

35 comments

I've always done that the inc.php thing, and it irks me when I see supposed professionals using /includes/filename.inc in an unprotected directory. It's just common sense, really.

Good article, though.
 United Kingdom #2: March 23, 2005
Nice little article, it's these simple mistakes that give php it's 'unsecure' rep.

Also you be double sure by adding .inc files to be parsed by php in your webroot folder.

AddType x-mapp-php4 .inc

or

AddType x-mapp-php5 .inc

should do the trick. On some crappy webhosts there is no access to a folder outside the webroot so this should add some protection.
 United Kingdom #3: March 30, 2005
Quote from article: Make sure this series changes each time the user tries to login, that it is an image (rather than simple text), and that it cannot be identified by an optical character recognition script.

Me: This kind of image is often called a captcha. They are used because it is extremely difficult to make a program that will be able to read the text that is in the image so bots cannot fill in the form.

Have a look at the Captcha Project Website: http://www.captcha.net/

Quote "zen-x": On some crappy webhosts there is no access to a folder outside the webroot so this should add some protection.

Me: Also on crappy webhosts .htaccess is disabled so that is totally useless anyway.
A common theme in recent PHP evaluation vulnerabilities is the use of the 'e' modifier in preg_ functions. This causes matched/modified parts of the pattern to be fed to eval() behind the scenes, and is *super* dangerous.

(See http://us4.php.net/manual/en/reference.pcre.pattern.modifiers.php )
John
United States #5: April 27, 2005
Great articles! Keep em' coming! :)
Nice article, really gets you thinking! I suppose i have already fixed the problems on my sites but most of them not on purpose. :) Good work!
And the problem with CAPTCHAs is that they keep blind and low-vision users from using your site. Not a good idea.
 United Kingdom #8: May 1, 2005
Quote Jemaleddin: And the problem with CAPTCHAs is that they keep blind and low-vision users from using your site. Not a good idea.

That isn't strictly true, some large companies offer a voice captcha where the characters are read out insted of in an image. If anyone developes one would you send me a copy plz?
Will
United States #9: May 14, 2005
Thanks, the articles helped me knock off something that may of gotten me in trouble later on.
paulg
France #10: May 18, 2005
Paranoid about files in your include directory being seen 'naked' because a change in settings somewhere? Add this line to the top of the file that you want to include.

<?php

if(strtolower(__FILE__) === $_SERVER['SCRIPT_FILENAME']){exit;}

?>

tested on w2k/php5 hence strtolower() :) Seems to work well.
I hope that using htaccess to protect 'includes' and other such directories without a password gives me some defense - but I've learnt a lot from these articles.
My host leaves globals on and fopen on - nothing they will do about that <sigh>
It is irritating when hosts do that, John. Especially as Safe Mode is designed just for them and turns things like register_globals off.

You can use ini_set to turn register_globals off at the beginning of every script. It can be worth spending a little time coming up with a list of a few php.ini options you'd like to change and setting them through an include at the beginning of every script, just to be on the safe side ...
Thanks for the tips, they are good. One thing I didn't see was using strong input validation; Not only escaping certain chacters in string frields, but checking length and data type as well. Not only should input validation be done client-side but it should be done server-side too. With some databases it is possible to build parameterized queries to call stored procedures and not use string contenation to build SQL statements. Encryption of cookies that store sensitive data also seems to be a good idea.
Blake
United States #14: June 19, 2005
Quote "You can use ini_set to turn register_globals off at the beginning of every script."

This does not work, as stated on php.net: "Just because you're able to set something
doesn't mean it will work as expected. Depends on the
setting. For example. setting register_globals at
runtime will be of little use as its job has already
been completed by the time it reaches your script."
BRAVO!

I read both your articles and I discover at least 3 vulnerabilitys on my PHP scripts.

The predictability is real a serious security whole, the most common examples are Windows, phpnuke etc, where every of those applications are in most cases unsetted, predictable and not secured by default.

I'm new in PHP-MySQL programming so I find your articles very useful than those I found on php.net, you really explain greate the security problems that a developer can encounter in making a dynamic web site.

I think that it will be greate if you put some more exaples for people like me that are new in this area so we can find here really a good source on how to secure our hand made web sites.

Another aspect of programming that I think you can argue here is to explane to people to not use the premade engins like PHP Nuke but to encourage tham to make they own code to make their script really not predictable.

The MySQL section, from my point of view, can be improved with more information, examples and maybe scripts-querys, but I think that you will do it in a short future.

At final I like to say that you do a good job here man and I like to encourage you to do more articles about PHP and MySQL and all fake pros that will bed comment your articles are wrong because you just try to help people and they not, so go ahead man ;).

Shabanese
Mellou
Germany #16: August 2, 2005
Quoting Matt: "This kind of image is often called a captcha. They are used because it is extremely difficult to make a program that will be able to read the text that is in the image so bots cannot fill in the form."

It might be extremely difficult, but not impossible â&#8364;&#8220; check out http://www.cs.berkeley.edu/~mori/gimpy/ez/.
An excellent piece of work for educating the PHP new comers to get a better habit of writing secured code from the very begining. A very nice cause for PHP Community at large.
Kindly keep the good work on. Would also like to contribute once reach that level.
Best of Regards

Rakesh
Nice article, really gets you thinking about what some users can do... Thanks!
Thanks for the nice articles!
dreamscape
United States #20: January 21, 2006
Quote: "If you detect a certain number of failed logins in a row, disable logging in to the administration area until it is reactivated by someone responsible."

Great article, but I do not agree with this. You are just trading one potential security risk for another. Disabling accounts leaves you open to Denial of Service attacks where the hacker is not trying to get in, but is purposely disabling a users account, which is quite easier to carry out than a dictionary attack I might add.

I think the key is to make any attack too expensive to do. Dictionary and brute force attacks are based on the premise that one can repeatedly hit the page in rapid succession.

So, even doing something as simple as a 2 or 3 second delay in the login script would make these kind of attacks too expensive to do for most people. And it would not significantly hinder usability (a small delay on a single page that is accessed once per session most of the time, will not be that noticeable). And also it doesn't open up a new kind of attack.

Of course there are many ways to combat dictionary and brute force attacks, and no one way is the best way all the time, but really I think that disabling accounts isn't that good of an idea when you consider that it opens you up to a extremely simple to execute denial of service attack, for which it would be extremely trivial to create a perpetual script that attempts to keep a user continually locked out of their account. All you would need to know is their username (the time span of the lockout would also help, but it is not necessary).

well, JIMHO
Slovenian
Slovenia #21: February 4, 2006
Hello, I really like your article. People really use predictable directories like /includes and they often let PHP display error messages. This obviously could not happen on this site.

http://www.addedbytes.com/includes
Heh, Slovenian. Nicely spotted and thankyou for the heads up. That isn't, unfortunately, an includes directory - that's a product of my caching system, that appears to be having a little difficulty. You get the same thing if you go to http://www.addedbytes.com/bananas (and while a hacker can grab a couple of filenames as a result of this bug, there's nothing more they can do with it).
Keith
Canada #23: March 9, 2006
Quote: "Placing important files in predictable places with predictable names is a recipe for disaster."

As long as I name my files .php what is the problem? I have seen several sites that tell you to put the includes outsite of the root, but I have not seen an explanation as to why this is critical - as long as the file is parsed?

Thanks.
Hi Keith.

It's not the code that is the risk, it's what the code can do. Administration files can delete and modify information, and administration areas have the same capabilities - someone managing to access these files could cause serious problems to a site.
This one is a bit an expansion and elaboration to former part1 .
I missed the usefull code examples you used in part 1.
How about buffer overflows?
Maybe you could be elaborating cross site scripting attacks?
More methods of sql injection?
Maybe a "best way to secure" part with the code examples to use for the newbies among us.
And please get that link for a printable version.
I of to read part 3
alex boia
Romania #26: August 20, 2006
ok..so there are a lot of comments and so little time for me to read them. so i'm sorry if what that the topic this reply adresses has been already covered. but enough with the smalltalk.:)
what i wnat to say is that if you want to protect your inlcuded php files to be executed from outside the script that uses them you can use something like:
<?php
define ('IS_INCLUDED_SOMEFILE', true);
require_once 'somefile.php';
?>
in the script that requires the file and
<?php
if (!defined('IS_INCLUDED_SOMEFILE')) exit;
if (IS_INCLUDED_SOMEFILE !== true) exit;
?>
in the required file.
works very fine, as you define this constant only where you need to include the files
?>
Don't use safe mode, it will be dropped in PHP6
Anonymous
Denmark #28: December 18, 2006
I do like this.. at the top of the include file.

if ($ping != "pong")
{
echo 'Redirect here, or print something :p';
exit;
}

then in the script that's including it:

$ping = "pong";
include(yourfile.....);

Seems to work fine too.
I have found the best way for looking at input, is to check the value for http or the length of the input as well as commands that should not be in there like ? = , as these script kiddies will do anything to hack your programs.
All the best from Alan
Good article. Event it is from 2005 the issues are still there. Advanced search technics like google's inurl makes it realy easy to find sites which use include folder, .inc files or whatever you are interestet in.
Jamie
Canada #31: March 3, 2008
Great article! Thanks for sharing your knowledge!

Is there any reason you can't use .htaccess to protect your .inc files (like below) without putting them in an /include folder?

.htaccess file:
<Files ~ "\.inc$">
Order allow,deny
Deny from all
</Files>

I was under the impression this was safe...
Web
Romania #32: March 19, 2008
Thank you so much for the posts! Very very helpful, I appreciate you for writing these security issues in PHP, I've learned quite a bit! CHEERS!
Sandor
Unknown #33: September 11, 2008
never trust user data, should we trust a webpage as well :-)

I think banning an IP address is very dangerous, because if it's from a public provider with many users like AOL etc. than all other users from that provider will banned as well.

-ciao Sandor-
Erick
United States #34: October 27, 2008
You mention adding a delete indicator on a record. I go beyond that on every table. I add:

update_datetime
update_user_id
create_datetime
create_user_id
delete_datetime
delete_user_id

Then, anything that is done is logged. At least the last execution of any of those. If you need more granularity in something, then create a table that logs extended or historical information.
Fredrik
Sweden #35: June 17, 2009
Thank you for these articles, a very good read!

A note about using .htaccess files to restrict access, if you go from Apache to Lighttpd like I just did, they stop working since Lighttpd doesn't seem to support them. You need to set, for example, url.access-deny in your lighttpd.conf instead.

Post Your Comment

· Comments with keywords instead of a name have their URLs removed.
· Your email address will not be displayed or shared.

Live Comment Preview

 United States #36: 1 minute ago