As the majority of websites run off PHP, application security is an important topic for PHP developers to protect their website, data, and clients. This topic covers best security practices in PHP as well as common vulnerabilities and weaknesses with example fixes in PHP.
- Preventing SQL Injection with Parameterized Queries in PDO
- Prepared Statements in mysqli
- Open Web Application Security Project (OWASP)
Command Line Injection
In a similar way that SQL injection allows an attacker to execute arbitrary queries on a database, command-line injection allows someone to run untrusted system commands on a web server. With an improperly secured server this would give an attacker complete control over a system.
Let's say, for example, a script allows a user to list directory contents on a web server.
(In a real-world application one would use PHP's built-in functions or objects to get path contents. This example is for a simple security demonstration.)
One would hope to get a
path parameter similar to
/tmp. But as any input is allowed,
path could be
; rm -fr /. The web server would then execute the command
and attempt to delete all files from the root of the server.
All command arguments must be escaped using
escapeshellcmd(). This makes the arguments non-executable. For each parameter, the input value should also be validated.
In the simplest case, we can secure our example with
Following the previous example with the attempt to remove files, the executed command becomes
And the string is simply passed as a parameter to
ls, rather than terminating the
ls command and running
It should be noted that the example above is now secure from command injection, but not from directory traversal. To fix this, it should be checked that the normalized path starts with the desired sub-directory.
PHP offers a variety of functions to execute system commands, including
system. All must have their inputs carefully validated and escaped.
Cross-Site Request Forgery
Cross-Site Request Forgery or
CSRF can force an end user to unknowingly generate malicious requests to a web server. This attack vector can be exploited in both POST and GET requests. Let's say for example the url endpoint
/delete.php?accnt=12 deletes account as passed from
accnt parameter of a GET request. Now if an authenticated user will encounter the following script in any other application
the account would be deleted.
A common solution to this problem is the use of CSRF tokens. CSRF tokens are embedded into requests so that a web application can trust that a request came from an expected source as part of the application's normal workflow. First the user performs some action, such as viewing a form, that triggers the creation of a unique token. A sample form implementing this might look like
The token can then be validated by the server against the user session after form submission to eliminate malicious requests.
Here is sample code for a basic implementation:
There are many libraries and frameworks already available which have their own implementation of CSRF validation. Though this is the simple implementation of CSRF, You need to write some code to regenerate your CSRF token dynamically to prevent from CSRF token stealing and fixation.
Cross-Site Scripting (XSS)
And a PHP application directly outputs a string passed into it:
If an unchecked GET parameter contains
<script src="http://example.com/runme.js"></script> then the output of the PHP script will be:
As a general rule, never trust input coming from a client. Every GET, POST, and cookie value could be anything at all, and should therefore be validated. When outputting any of these values, escape them so they will not be evaluated in an unexpected way.
Keep in mind that even in the simplest applications data can be moved around and it will be hard to keep track of all sources. Therefore it is a best practice to always escape output.
PHP provides a few ways to escape output depending on the context.
htmlspecialchars will convert any "HTML special characters" into their HTML encodings, meaning they will then not be processed as standard HTML. To fix our previous example using this method:
Everything inside the
When outputting a dynamically generated URL, PHP provides the
urlencode function to safely output valid URLs. So, for example, if a user is able to input data that becomes part of another GET parameter:
Any malicious input will be converted to an encoded URL parameter.
Using specialised external libraries or OWASP AntiSamy lists
Sometimes you will want to send HTML or other kind of code inputs. You will need to maintain a list of authorised words (white list) and un-authorized (blacklist).
You can download standard lists available at the OWASP AntiSamy website. Each list is fit for a specific kind of interaction (ebay api, tinyMCE, etc...). And it is open source.
There are libraries existing to filter HTML and prevent XSS attacks for the general case and performing at least as well as AntiSamy lists with very easy use. For example you have HTML Purifier
By default PHP will output errors, warnings and notice messages directly on the page if something unexpected in a script occurs. This is useful for resolving specific issues with a script but at the same time it outputs information you don't want your users to know.
Therefore it's good practice to avoid displaying those messages which will reveal information about your server, like your directory tree for example, in production environments. In a development or testing environment these messages may still be useful to display for debugging purposes.
A quick solution
You can turn them off so the messages don't show at all, however this makes debugging your script harder.
Or change them directly in the php.ini.
A better option would be to store those error messages to a place they are more useful, like a database:
This method will log the messages to the database and if that fails to a file instead of echoing it directly into the page. This way you can track what users are experiencing on your website and notify you immediately if something go's wrong.
Remote File Inclusion
Remote File Inclusion (also known as RFI) is a type of vulnerability that allows an attacker to include a remote file.
This example injects a remotely hosted file containing a malicious code:
Local File Inclusion
Local File Inclusion (also known as LFI) is the process of including files on a server through the web browser.
Solution to RFI & LFI:
It is recommended to only allow including files you approved, and limit to those only.
PHP Version Leakage
By default, PHP will tell the world what version of PHP you are using, e.g.
To fix this you can either change php.ini:
Or change the header:
Or if you'd prefer a htaccess method:
If either of the above methods do not work, there is also the
header_remove() function that provides you the ability to remove the header:
If attackers know that you are using PHP and the version of PHP that you are using, it's easier for them to exploit your server.
strip_tags is a very powerful function if you know how to use it. As a method to prevent cross-site scripting attacks there are better methods, such as character encoding, but stripping tags is useful in some cases.
Say you wanted to allow a certain tag but no other tags, then you'd specify that in the second parameter of the function. This parameter is optional. In my case I only want the
<b> tag to be passed through.
HTML comments and
PHP tags are also stripped. This is hardcoded and can not be changed with allowable_tags.
PHP 5.3.4 and later, self-closing
XHTML tags are ignored and only non-self-closing tags should be used in allowable_tags. For example, to allow both
<br/>, you should use:
If you want users to upload files to your server you need to do a couple of security checks before you actually move the uploaded file to your web directory.
The uploaded data:
This array contains user submitted data and is not information about the file itself. While usually this data is generated by the browser one can easily make a post request to the same form using software.
name- Verify every aspect of it.
type- Never use this data. It can be fetched by using PHP functions instead.
size- Safe to use.
tmp_name- Safe to use.
Exploiting the file name
Normally the operating system does not allow specific characters in a file name, but by spoofing the request you can add them allowing for unexpected things to happen. For example, lets name the file:
Take good look at that filename and you should notice a couple of things.
- The first to notice is the
../, fully illegal in a file name and at the same time perfectly fine if you are moving a file from 1 directory to another, which we're gonna do right?
- Now you might think you were verifying the file extensions properly in your script but this exploit relies on the url decoding, translating
nullcharacter, basically saying to the operating system, this string ends here, stripping off
.pngoff the filename.
So now I've uploaded
script.php to another directory, by-passing simple validations to file extensions. It also by-passes
.htaccess files disallowing scripts to be executed from within your upload directory.
Getting the file name and extension safely
You can use
pathinfo() to extrapolate the name and extension in a safe manner but first we need to replace unwanted characters in the file name:
While now we have a filename and extension that can be used for storing, I still prefer storing that information in a database and give that file a generated name of for example,
This would resolve the issue of duplicate file names and unforseen exploits in the file name. It would also cause the attacker to guess where that file has been stored as he or she cannot specifically target it for execution.
Checking a file extension to determine what file it is is not enough as a file may named
image.png but may very well contain a php script. By checking the mime-type of the uploaded file against a file extension you can verify if the file contains what its name is referring to.
You can even go 1 step further for validating images, and that is actually opening them:
White listing your uploads
Most importantly, you should whitelist file extensions and mime types depending on each form.