Writing Secure PHP
Six security vulnerabilities often overlooked when writing code without the use of a web framework especially for an unseasoned PHP programmer are:
- Cross Site Scripting (XSS)
- SQL Injection
- Cross-Site Request Forgery (CSRF)
- Path Traversal
- Code Injection, and
- File Upload Attacks
In each section of this blog post I will discuss each of these exploits individually in detail. I have divided each section into two parts: the first part explains how to identify the vulnerability covered in that section in your code and the second part provides recommendations for fixing the vulnerability.
Let’s get started.
Cross Site Scripting (XSS)
An XSS exploit occurs when malicious code submitted by a user (usually as part of a HTTP post) is allowed to be executed by the browser without first being checked. Any application that displays user-submitted content in the browser is vulnerable to this type of threat. Take for example an application that allows users to register for a service that requires verification of the submitted content before it is saved to the database. A trivial form to facilitate this is shown below:
Once a user submits this form the application displays the content back to the user once the page reloads for them to verify their information. A simple example for the code that deals with the page reload is shown below:
Though this might look ok at a glance, on closer inspection a few vulnerabilities exist in this script. Lets assume that the user submits the form shown in Example 1 with the code below in either the first or last name fields.
This would essentially allow the code above to be executed by the browser sending the current page cookies to the steal.php script residing on evil.example.org for processing. It is safe to say this was not the original purpose of the form.
Recommendations For Preventing XSS
XSS vulnerabilities usually arise from not properly escaping user-submitted content. The simplest solution for addressing these types of threats is to use php’s built-in function htmlspecialchars. The htmlspecialchars function converts special characters to HTML entities which the browser then recognized as markup to be rendered and not script to be executed. A revised version of the code from Example 2 correctly escaped is shown below:
SQL Injection exploits a security vulnerability whereby one or more sub-queries are intentionally submitted as part of a HTTP request. For example, from a login form which then gets executed as part of a valid backend’s query. The inserted sub-queries are used to change the intent of the backend query, for instance, to gain unauthorized access to a web application. Consider this simple login form:
By viewing the source code for this form a hacker can make logical guesses about the associated database schema, she could deduce that the database and column names are ‘users’, ‘username’ and ‘password’ respectively and therefore conclude that the query used to validate the above form would look something like this:
Armed with this knowledge she could then submit the following values:
Username: myuser’ or ‘foo’ = ‘foo’ —
Password: invalid password
The interpreted SQL statement would look something like this:
Since the double dashes (—) shown in the example above begins an SQL comment, the query is effectively terminated at that point allowing the hacker to log in successfully without knowing either a valid username or password.
Recommendations for preventing SQL Injection
In general, you should never concatenate data directly into SQL queries. You should at all cost avoid doing the following:
Instead, use a solution that supports parameterized SQL such as the MySQLi or PDO_MySQL extension or a database abstraction layer that also supports parameterized SQL.
If for some reason you are unable to transition immediately to a parameterized based solution and your application is running a version of PHP less than 5.5, another possible solution would be to use PHP’s built-in mysql_real_escape_string function to escape variables passed to SQL statements. See example below.
It is worth noting here that mysql_real_escape_string is deprecated in PHP 5.5 and later.
What Is Cross-Site Request Forgery (CSRF)
CSRF exploits occur when a user’s action that requires either authentication or authorization, or both, is circumvented by hijacking the user’s session. A web application is vulnerable to this type of attack if:
- the web application uses session cookies and
- acts on an HTTP request without verifying that the request was made with the user’s consent.
If the request does not contain a unique token that proves its authenticity, the code that handles the request is vulnerable to a CSRF attack.
This means a Web application that uses session cookies has to take special precautions in order to ensure that a hacker can not trick users into submitting bogus requests. Lets consider a web application that allows administrators to create new accounts by submitting a form similar to the one shown below:
A hacker might set up a web site with the following:
If an administrator for example.com visits the malicious page while having an active session on the site, they would unwittingly create an account for the attacker. This is a CSRF attack. It is possible because the application does not have a way to determine the authenticity of the request. Any request could be a legitimate action chosen by the user or a faked action set up by an attacker. The attacker does not get to see the web page that the bogus request generates, so the attack technique is only useful for requests that alter the state of the application.
Recommendations For Preventing CSRF
Here are a couple measures you can take to reduce the risk of CSRF attacks. Either may be used separately or in combination as you see fit.
- Require verification for critical actions such as credit card payments and account profile changes.
- The second approach ensures the authenticity of a request by including a unique token in every form post. The backend code for each form then uses this token to validate the request. Consider the following replacement for the HTML form in Example 11:
The token can be checked with a simple conditional statement:
With the inclusion of the token in the forms, you practically eliminate the risk of CSRF attacks. Again, keep in mind that this is a simple example just for demonstrative purposes as more could be done in the way the tokens are generated and verified. A token should be used for any form that performs an action.
This type of exploit allows attackers to access files and directories located outside your application root directory. This is done by altering the input off operations used to read files, which allows the attacker to access or modify otherwise protected files. A path traversal vulnerability exists when the following two conditions are met:
- An attacker can specify a path used in an operation on the filesystem.
- By specifying the resource, the attacker gains a capability that would not otherwise be permitted.
For example, consider the following code snippet.
To make the vulnerability more obvious, this example uses $_GET. An acceptable value for the query string ‘source’ might look like this:
In this example it is possible to change the intent of this string by doing the following:
http://example.org/statements.php?file=../../../../some dir/some file
Which allows an attacker to access otherwise protected files.
Recommendations For Preventing Code Injection
Two precautionary measures that should be taken to mitigate the occurrence of path traversal exploits are:
- Selectively reject or escape potentially dangerous characters such as back and forward slashes before using user inputs.
- Check to ensure that the resource requested is from a given directory or list of directories before returning a response to the user. This can be implemented by using either a black or white list.
In the above code the sanitize_source function is used to remove illegal characters from the source variable. The check_file_location function is used to check the source against a white list and returns true if a match is found.
What Is Code Injection
A code injection attack is used to introduce (or “inject”) code into an application to change the course of execution. One of the more obvious causes of code injection is the use of tainted data as the leading part of a dynamic file path. See demonstration below:
This allows an attacker to manipulate the nature of the resource to be included. Lets say an attacker was to do this:
This causes the inclusion and execution of code of the attacker’s choosing.
Recommendations For Preventing Code Injection
See recommendations for preventing path manipulation.
File Upload Attacks
Uploaded files are commonly associated with three types of attacks:
- Denial of service
- Viruses and other Malware
A file upload vulnerability exists when files are uploaded to a web server without being validated to ascertain the file type. This may result in unchecked files being executed and adversely impacting the server or system your application runs on.
Recommendations For File Upload Attacks
Specify as part of your application business logic what file type should be allowed to be uploaded by using a white list. The code snippet below shows a simple function that checks the temporary uploaded file based on its mime type.
If the uploaded file does not match a legitimate type it should not be processed further (if the file is not moved from the tmp directory PHP usually deletes it once the upload script finishes execution).
As added precautions, you should also consider restricting the size of the files users are allowed to upload and scan files for viruses before permanently saving them with an anti virus software such as ClamAV®.
There are many other web application exploits that were not touched on in this blog post, such as JSON Hijacking and Clickjacking, these I hope to cover separately in future posts. Identifying security vulnerabilities in your application’s code is as equally important as fixing them. As a rule of thumb, you should always filter inputs submitted by users to ensure that the data submitted is valid, and escape data sent to the database or the screen for display.
 “Essential PHP Security” Chris Shiflett.
 Also see owasp.org