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.
37 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.
#1, Phil Sherry, United Kingdom, 22 March 2005. Reply to this.
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.
#2, zen-x, United Kingdom, 23 March 2005. Reply to this.
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.
#3, Matt, United Kingdom, 30 March 2005. Reply to this.
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 )
#4, Chris Snyder, United States, 23 April 2005. Reply to this.
Great articles! Keep em' coming! :)
#5, John, United States, 27 April 2005. Reply to this.
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!
#6, Patrik, Sweden, 27 April 2005. Reply to this.
And the problem with CAPTCHAs is that they keep blind and low-vision users from using your site. Not a good idea.
#7, Jemaleddin, United States, 27 April 2005. Reply to this.
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?
#8, Matt, United Kingdom, 1 May 2005. Reply to this.
Thanks, the articles helped me knock off something that may of gotten me in trouble later on.
#9, Will, United States, 14 May 2005. Reply to this.
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.
#10, paulg, France, 18 May 2005. Reply to this.
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>
#11, John Harrison, United Kingdom, 20 May 2005. Reply to this.
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 ...
#12, Dave Child, United Kingdom, 20 May 2005. Reply to this.
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.
#13, Doug Coombs, United States, 15 June 2005. Reply to this.
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."
#14, Blake, United States, 19 June 2005. Reply to this.
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
#15, Shabanese, Italy, 22 July 2005. Reply to this.
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;“ check out http://www.cs.berkeley.edu/~mori/gimpy/ez/.
#16, Mellou, Germany, 2 August 2005. Reply to this.
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
#17, Rakesh Sharma, India, 18 October 2005. Reply to this.
Nice article, really gets you thinking about what some users can do... Thanks!
#18, Module47, Netherlands, 10 November 2005. Reply to this.
Thanks for the nice articles!
#19, Sam, Finland, 28 December 2005. Reply to this.
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
#20, dreamscape, United States, 21 January 2006. Reply to this.
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
#21, Slovenian, Slovenia, 4 February 2006. Reply to this.
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).
#22, Dave Child, United Kingdom, 4 February 2006. Reply to this.
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.
#23, Keith, Canada, 9 March 2006. Reply to this.
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.
#24, Dave Child, United Kingdom, 9 March 2006. Reply to this.
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
#25, shopje, Netherlands, 20 March 2006. Reply to this.
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
?>
#26, alex boia, Romania, 20 August 2006. Reply to this.
Don't use safe mode, it will be dropped in PHP6
#27, Frederik, Netherlands, 8 December 2006. Reply to this.
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.
#28, Anonymous, Denmark, 18 December 2006. Reply to this.
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
#29, Alan Walker, United Kingdom, 20 February 2008. Reply to this.
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.
#30, Malanie, Germany, 24 February 2008. Reply to this.
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...
#31, Jamie, Canada, 3 March 2008. Reply to this.
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!
#32, Web, Romania, 19 March 2008. Reply to this.
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-
#33, Sandor, Unknown, 11 September 2008. Reply to this.
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.
#34, Erick, United States, 27 October 2008. Reply to this.
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.
#35, Fredrik, Sweden, 17 June 2009. Reply to this.
Excellent article.
However I agree with Jamie from Canada above, it *is* safe to use .inc files (and indeed an /include dir) if you set up your htaccess rules so they are not visible externally (or alternatively you can place them outside the apache docroot).
In the following article Rasmus Lerdoff (the original author of PHP) uses this approach (and there is a dicsussion of this exact issue in the comments, see #37 and onwards):
http://toys.lerdorf.com/archives/38-The-no-framework-PHP-MVC-framework.html
#36, Dougall, United Kingdom, 13 October 2009. Reply to this.
That's why all my includes end with .php as .inc can be exposed as text files in browsers. I don't see the point of adding .inc.php though. Just stating, "inc_db.php" is informative and you sort them naturally.
#37, Jeff Majors, 26 October 2010. Reply to this.