PHP is probably not the sexiest language out there today. Developers usually prefer more current tools, such as NodeJs or Go, for their new projects. PHP has a lot to offer (especially since version 7.0 came to town), but that’s a discussion for another article.
The fact is, no matter how much you like or dislike PHP, a great number of websites are powered by it. Chances are you’re going to bump into PHP sooner or later so you’d better be prepared, right?
Due to PHP’s loose structure and ease of use, there’s a lot of PHP code out there that either hasn’t been written by professionals or doesn’t follow the most up-to-date industry best practices. This implies, among other problems, the abundance of security vulnerabilities in such systems.
As a web developer, it’s up to you to keep your client’s information safe. In this post I’m going to show you:
XSS (Cross Site Scripting)
How can this happen?
Let’s look at a simple example:
It’s almost the simplest script you can write using PHP (right below to the classic “Hello World!”).
Suppose a visitor runs your code using a URL such as: http://yourdomain.com/index.php?name=’Mauro’
The result will be a simple page:
No harm done, right?
So what’s the problem?
Which produces a slightly different result:
How did this happen?
Which produces this code on the user’s browser:
That doesn’t look good, does it?
Then again, showing an alert doesn’t really hurt anyone, does it? Besides, the user is clearly aware that something odd is happening.
Let’s try a more elaborate example.
Which translates into this HTML:
This means that, without the user noticing anything, their cookies have been sent to: http://malicious.com.
Now this can really be serious.
Imagine if at the other end of this URL is a script carefully designed to get those cookies and make new requests to your site. Your server will process them as being issued by the original user, who immediately becomes the victim of the attack.
Before we move on, let’s address the elephant in the room: Who would use such a link on their own browser? No one, of course. But if, somehow, an attacker was able to send this particular URL as a link inside an email, someone might be tricked into clicking, which is when the trouble begins.
As to the damage this attack could potentially cause, the list includes data theft, unauthorized access, malware distribution, and showing inappropriate content. The list is practically endless, hence the need to prevent this from happening.
How Do You Prevent XSS from Happening in PHP?
In order to determine how to fix your application, you first need to understand the factors that allow the attack to be successful. In this case the problem is that the data that is output to the client’s browser is not validated nor sanitized. A simple precaution would be to transform the script tags into innocent HTML. One way to do that is to use a built-in function called htmlentities.
If the code you saw earlier had implemented it:
Neither of the attacks shown would work, basically because the browser would receive the following HTML code:
Which effectively renders to:
So the malicious intent is clearly exposed but never executed.
The second most common vulnerability is called SQL Injection. In this scenario, the application allows an attacker to execute arbitrary code into its database. Let’s look at an example of how a successful attack could be achieved.
Suppose your website is a blog that allows users to leave comments. You’d probably have a form like this:
The file add_comment.php would look somewhat similar to:
As in the previous example, nothing bad happens as long as the user behaves as expected (meaning, they enter a legitimate comment), but then again, we’re not here to talk about what regular users do, are we?
What happens when a malicious user fills his comment with a’); delete from comments; — ?
The executed SQL is:
A.K.A. bye bye comments!
Side note: if you prefer a graphical explanation, don’t miss this excellent comic by xkcd
How to Prevent SQL Injection Attacks in a PHP Application
The problem here is that the SQL sent to the database is not known when you’re writing the code since a part of it comes from external input: ($_POST[‘comments’]).
In order to prevent malicious code to be injected into your legitimate query, you have two options:
- Make sure no malicious code is added to the query
- Make sure that should malicious code be added to the query, it never gets executed
Option 1 is possible when you know the possible values an input can take (for example, a dropdown list) or at least the type of data you should get from it (for example, an age). In this case, you can’t use this option since anyone can make any comment. You have to resort to option 2.
How do you prevent malicious code from being executed? Pretty much the same way you saw in preventing XSS attacks: you sanitize the input before using it. There are a few methods to do so using PHP. My favorite is using PDO’s prepared statements.
The fixed code looks like this:
The difference is that now, before executing the sentence, PDO takes care of escaping the parameter for you. This means adding a \ before every ‘ which means the resulting SQL sentence is:
This results in a rather weird comment but nothing else.
Command injection attacks work very similarly to SQL Injection ones. The main difference is that, while with SQL injection the target is the database, in command injection, it is the system itself. This attack is performed by running arbitrary commands using the server’s operating system.
How can this happen?
Let’s look at an example where your application lists the files contained in a directory matching the provided user name, something like:
If the script receives a valid username everything works just fine, but an attacker could submit a username like /; rm -Rf * which means the executed command is: cd /; rm -Rf *; ls -l
Ouch. I hope you have updated backups at hand.
How to Prevent Command Injection Attacks in a PHP Application
For a command injection attack to succeed, three things must happen at the same time:
- The application must make use of a system call (passthru, system or similar).
- The command passed as argument to the function must be created using some external input
- The input used must not be validated or sanitized
Now that you know how this vulnerability can be present, it’s only a matter of removing any one of these to make it go away. The best way to approach this is to attack number one, meaning avoid using system calls whenever possible. This may sound odd. If the application needs to rely on some OS functionality, there’s no way around it, however, most of the time you have the option to use PHP’s built-in functions to interact with the OS. For instance, if you need to know which files are present in a directory you can use opendir and readdir. If you want to delete a file you can use unlink instead of system(“rm $file”) and so on.
Of course, sometimes you just can’t help but use a system call. Perhaps you need to execute a python script for some reason. In this case, try to avoid generating the command on-the-fly. If this is not possible, you have the option either to validate the input you’re about to use to generate the command or to use some escaping function like escapeshellcmd or escapeshellarg.
Cross Site Request Forgery (CSRF)
Last but not least, we have to discuss CSRF. In this attack (also known as session riding), the attacker tricks your webserver into believing an authenticated user is issuing a request.
The idea is very simple. Some users log into your site and, while having their session open they get a notification of a very exciting opportunity to help some foreign prince get a considerable amount of money out of his country. All they need to do is click on a link to get more information.
What this link actually does is send a request to your website, which your server can’t distinguish from a legitimate one.
How to Prevent CSRF Attacks in a PHP Application
In order to prevent CSRF attacks you need to make sure the request you’re getting actually comes from the user you believe. How do you do that?
One very common way of doing this is by creating an extra input that holds secret information. An example will help clarify.
Let’s use the blog site again. In the comment form you’d have this code:
And at the add_comment.php side:
Never Trust External Data
As you saw in the examples above, the main reason these attacks can succeed lies in trusting external input. One thing I want to stress is that “external input” isn’t limited to $_GET or $_POST, not at all!
A cookie (or any other HTTP header for that matter) can carry malicious code. The same happens for the contents of a text file, a web service response, etc., so, keep your eyes open for anything you don’t have 100% control over.
Bonus: a Tool to Check Your Dependencies’ Vulnerabilities
Before you go, I want to give you a little perk for reading this far 🙂
Since you’re working with php, chances are you are using composer for your third party dependencies. If that’s the case then you definitely want to check out this tool. It’s a little command line script designed to check for known vulnerabilities found in packages your application uses. Pretty neat, isn’t it?
Putting It All Together
In this post you learned about:
- The most common vulnerabilities found in PHP-based web applications
- How those vulnerabilities can be exploited
- What harm can be done if an attack succeeds
- What you can do to avoid security issues in your code
Hopefully it’s clear to you that security threats are to be taken seriously and that hardening your applications is not that big of an effort, so stay alert and be safe.