Writing Secure PHP Applications How things can go wrong? How to prevent the disaster? Never, ever, trust user inputs Input Validation Always use server side validation as client side (javascript) validation can easily be bypassed Use white-listed values Use built-in escape functions Validate for correct data types, like numbers Don’t expect the return value from selections, radio buttons or check boxes of a form to be the ones you mentioned. So, always revalidate. PHP Error Messages Great tool for both developer and hacker. Can reveal server software and directory structure, even database login information. Need to set error_reporting to 0 using php.ini or .htaccess file in production server. Should have separate .htaccess files for development and production servers. Use proper error handlers when necessary (instead of just showing the error message and die()ing ) Further Reading : http://perishablepress.com/press/2008/01/14/advanced-phperror-handling-via-htaccess/ Bad Features of PHP Register Globals Consider the following code test.php if ($password == "my_password") { $authorized = 1; } if ($authorized == 1) { echo "Lots of important stuff."; } test.php?authorized=1 will produce “Lots of important stuff.” To disable register_globals using .htaccess file – php_flag register_globals 0 To disable register_globals using php.ini – register_globals = Off Bad Features of PHP Magic Quotes Originally introduced to save programmers the trouble of using addslashes() Major problems associated with it is – a. When both magic quotes and addslashes() are used, you end up with multiple slashes being added, causing errors b. If you think magic quotes is turned on, but actually it is not, then inputs go unchecked. c. It only escapes double and single quote, but there are other database-specific characters that need to be escaped Bad Features of PHP Magic Quotes Disabling using .htaccess – php_flag magic_quotes_gpc 0 php_flag magic_quotes_runtime 0 Disabling using php.ini – magic_quotes_gpc = Off magic_quotes_runtime = Off magic_quotes_sybase = Off SQL Injection Most common and most destructive security hazard Lets see the common way to check username and password entered into a form – $check = mysql_query("SELECT Username, Password, UserLevel FROM Users WHERE Username = '".$_POST['username']."' and Password = '".$_POST['password']."'"); If we enter the following in the “username” input box and submit ' OR 1=1 # The query that is going to be executed will now look like this – SELECT Username, Password FROM Users WHERE Username = '' OR 1=1 #' and Password = '' As you can see, this query will return all the users from the database and as generally first user on a user table is the admin, the hacker will easily gain admin privilege. We need to either use mysql_real_escape_string() on all database inputs (not just on string types) or use mysqli extension. File Manipulation Some sites currently running on the web today have URLs that look like this: index.php?page=contactus.html The user can very easily change the "contactus.html" bit to anything they like. For example, index.php?page=.htpasswd By changing the URL, on some systems, to reference a file on another server, they could even run PHP that they have written on your site. When users download a file from your server, if the file name depends on user input, he can easily manipulate it to download system files by giving inputs like – “../../../etc/passwd” File Manipulation To prevent remote and system files inclusion, make these changes in your php.ini file – 1. Correctly set “open_basedir” 2. Set “allow_url_fopen” to Off Always validate file names to make sure only valid characters are included – function isValidFileName($file) { /* don't allow .. and allow any "word" character */ return preg_match('/^(((?:\.)(?!\.))|\w)+$/', $file); } Use database and hidden, generated file names if possible for file downloads. Using Defaults MySQL uses default username of “root” and blank password SQL Server uses “sa” as default user with a blank password Hackers will first try to use the default username, password combination everywhere – be it a MySQL server or CMS like Wordpress, Joomla or Drupal. Make sure first thing you do after installing any software on server is to change default username, passwords. Also, create separate users other than “root” with appropriate permissions on MySQL. Leaving Installation Files Online Many PHP programs come with installation files. Many of these are self-deleting once run, and many applications will refuse to run until you delete the installation files. Many however, will not pay the blindest bit of attention if the install files are still online. If they are still online, they may still be usable, and someone may be able to use them to overwrite your entire site. Predictability If a hacker wants to gain admin access of your site, first location they will try to look into may be http://www.yoursite.com/admin/ Make sure your admin area is not predictable. Use something like – http://www.yoursite.com/jsfh8sfsifuhsi8392/ Same goes with username and password. Don’t use an username ‘admin’ or ‘administrator’. Pick something unusual Use a combination of lower and upper case letters along with digits to prevent dictionary attack. File Systems Always disable directory listing by placing a ‘index.html’ file in every folder.You can also disable by using .htaccess or httpd.conf file, but using ‘index.html’ will make sure you’re protected even if your server settings are changed. Don’t place common files in a directory like ‘www.mywebsite.com/includes/’. Use something out of web root for that and give it an unusual name. Don’t give a file the extension ‘.inc’ as it is usually rendered as text and can reveal the whole source code (and maybe database login information too). If you must use ‘.inc’, use ‘.inc.php’ instead as it will be rendered as php file. Session Hijacking By default, session information in PHP is written to a temporary directory.The file itself contains the information in a fairly readable format. As the file has to be readable and writable by the Web server user, the session files can create a major problem for anyone on a shared server. Someone other than you can write a script that reads these files so they can try to get values out of the session. Encrypt everything that you put into session. It is not completely safe though. Store session data in a different place, like a database. Storing Passwords Do not store password as plain text Do not try to invent your own password security Do not encrypt passwords as they are reversible. Security through obscurity is not sufficient. Do not use MD5 – though this cryptographic hashing function is irreversible , it is quite easy to make a list of millions of hashed passwords (a rainbow table) and compare the hashes to find the original passwords. MD5 is also prone to brute forcing (trying out all combinations with an automated script) because of collisions. Storing Passwords Do not use a single site-wide salt. A salt is a string that is hashed together with a password so that most rainbow tables (or dictionary attacks) won’t work. $password = 'swordfish'; $salt = 'something random'; $hash = md5($salt . $password);//Value: db4968a3db5f6ed2f60073c747bb4fb5 Use a cryptographically strong hashing function like SHA-1 or even SHA-256 (using PHP’s hash() function). Use a long and random salt for each password. Use a slow hashing algorithm to make brute force attacks near impossible. Regenerate the hash every time a users logs in. Storing Passwords Code to create the hash and salt $username = 'Admin'; $password = 'gf45_gdf#4hg'; // Create a 256 bit (64 characters) long random salt // Let's add 'something random' and the username // to the salt as well for added security $salt = hash('sha256', uniqid(mt_rand(), true) . 'something random' . strtolower($username)); // Prefix the password with the salt $hash = $salt . $password; // Hash the salted password a bunch of times for ( $i = 0; $i < 100000; $i ++ ) { $hash = hash('sha256', $hash); } // Prefix the hash with the salt so we can find it back later $hash = $salt . $hash; Storing Passwords Code to validate password $username = 'Admin'; $password = 'gf45_gdf#4hg'; $sql = ' SELECT `hash` FROM `users` WHERE `username` = "' . mysql_real_escape_string($username) . '" LIMIT 1 ;'; $r = mysql_fetch_assoc(mysql_query($sql)); // The first 64 characters of the hash is the salt $salt = substr($r['hash'], 0, 64); $hash = $salt . $password; // Hash the password as we did before for ( $i = 0; $i < 100000; $i ++ ) { $hash = hash('sha256', $hash); } $hash = $salt . $hash; if ( $hash == $r['hash'] ) { // Ok! } Login Systems You should add a turing test to your admin 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. 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. 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. 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. For example – eval("shell_exec(\"rm -rf {$_SERVER['DOCUMENT_ROOT']}\");"); Powerful Commands 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(), readfil e(), 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. Cross-Site Scripting (XSS) Unlike SQL Injection, which relies on the use of delimiters in user-input text to take control of database queries, code injection relies on mistakes in the treatment of text before it is output. Let's say you've not added a limit to username lengths. Someone could, if they wanted, create a user with the following username: username<script type="text/javascript" src="http://www.website.com/malicious.js"></script> Anyone that then views a page with that username on it will see a normal username, but a JavaScript has been loaded from another site invisibly to the user. Cross-Site Scripting (XSS) It allows attackers to add keyloggers, tracking scripts or porn banners on your site, or just stop your site working altogether. It can also used for cookie hijacking so that a real user can be faked. Always use htmlentities() function to output user-generated texts. Limit the character set that can used for a particular text type Disallow HTML input if possible. If that is not an option, only allow limited HTML tags Cross-Site Request Forgery (CSRF) CSRF attacks are exploits that take advantage of user privileges to carry out an attack. In a CSRF attack, your users can easily become unsuspecting accomplices. For example – <img src="http://www.example.com/processSomething?id=123456789" /> CSRF attacks are often in the form of <img> tags because the browser unwittingly calls the URL to get the image. However, the image source could just as easily be the URL of a page on the same site that does some processing based on the parameters passed into it. When this <img> tag is placed with an XSS attack — which are the most common of the documented attacks — users can easily do something with their credentials without knowing it — thus, the forgery. Cross-Site Request Forgery (CSRF) Never let the user do anything with a GET request - always use POST. Confirm actions before performing them with a confirmation dialog on a separate page - and make sure both the original action button or link and the confirmation were clicked. Add a randomly generated token to forms and verify its presence when a request is made. Time-out sessions with a short timespan (think minutes, not hours). Encourage the user to log out when they've finished. Check the HTTP_REFERER header (it can be hidden, but is still worth checking as if it is a different domain to that expected it is definitely a CSRF request). References How to store passwords safely with PHP and MySQL – http://elbertf.com/2010/01/store-passwords-safely-with-php-andmysql/ Writing secure PHP series – http://www.addedbytes.com/writing-secure-php/writing-secure-php-1/ http://www.addedbytes.com/writing-secure-php/writing-secure-php-2/ http://www.addedbytes.com/writing-secure-php/writing-secure-php-3/ http://www.addedbytes.com/writing-secure-php/writing-secure-php-4/ Seven habits for writing secure PHP applications by IBM http://www.ibm.com/developerworks/opensource/library/os-phpsecure-apps/index.html 5 Helpful Tips for Creating Secure PHP Applications http://net.tutsplus.com/tutorials/php/5-helpful-tips-for-creatingsecure-php-applications/ Finally, Be Completely and Utterly Paranoid Thank you