Skip to content.

Scott Arciszewski

Software, Privacy, Security, Innovation

Thwarting Basic Web Application Attacks

August 1, 2014 10:58 PM • Information Securty, PHP, Security, Migrated, Tutorial

This was originally posted on a website I was developing over a year ago called Keenotes.

If you're reading this article, then chances are that you're not an information security professional. So before I get into anything else, there is something you must understand: There is no such things as perfect security. It is a continuum from zero security (anyone and everyone can control your machine at every level) to the limits of human imagination. Security is a balance between cost and risk; and ideally the cost should be borne entirely by the attacker.

Web application security is often touted to be a difficult concept to understand and teach. Self-proclaimed experts in the field are plentiful, and often very pleased with themselves for earning that title (or buying a certification to compensate for lack of skill). Unfortunately, what they do doesn't require very much intelligence beyond a very basic understanding of computer science, programming, and how networks operate. (To anyone who might be reading this that put all their eggs in the web app security basket, sorry to be the one to tell you.)

To further defend my points (and promote better coding practices), I'm going to explain how to stop every common web application attack; complete with proof of concept code.

This article is outlined as follows:

  1. Introduction (you just read this part)
  2. Server Setup and Configuration
    1. Operating System Updates
    2. Getting the Ball Rolling
  3. The Attacks
    1. Session Security
    2. Local and Remote File Inclusion
    3. Password Hashing
    4. Securing File Uploads
    5. SQL Injection
    6. Cross-Site Scripting
    7. Cross-Site Request Forgery
  4. Other Things of Importance
  5. Resources

Server Setup

The first security decision you should make when deciding to build a website is, "Where/how will I host this website?" If you're cheap and lazy, shared hosting might be an attractive option; however, I strongly recommend you get a Virtual Private Server instead. Three reasons:

  1. They're priced competitively with shared hosting. (See also:
  2. You have full root access of your own operating system on your own (virtual) server; thus giving you more control and flexibility (which you will need if you wish to secure a server properly).
  3. No matter how secure your website may be, a security hole in another website built by someone else on the same shared server can (and probably will) lead to a compromise of every website on the server. In case it needs reaffirming, this is very bad.

However, if your finances are pristine, you will benefit greatly from ponying up the money to build and host your own dedicated server. Virtual machines are still theoretically hackable, and you can never know with 100% confidence that you can trust your VPS provider. They could, for example, be a member of a black hat hacking group and they use their VPS company to legitimize their computer usage and earn a reliable income between big bank hits. (These are the kind of things you should always consider, but not dwell on.)

So now that you have your dedicated or virtual server ready (if you're still opting to go with shared hosting, shame on you for being negligent), you're ready to begin.

For the examples, I will be using an Ubuntu 12.04 LTS server (a Linux server). If you want a tutorial with a Microsoft server, tough cookies. You're willing to pay for a Microsoft server, so you should be willing to pay for security advice. Don't like it? Learn to use Unix, Linux, or BSD servers for webhosting. If you think I'm being mean or unfair, consider the low intelligence required to want to be a security professional, and yet still trust a proprietary closed-source operating system that withholds critical security patches until Tuesdays.

Now that we've got the politics out of the way, let's begin with the basic server setup. I'm assuming you already know how to access your server with SSH or are in the process of searching the internet for a tutorial.

Operating System Updates

The first thing you want to do is update your server. If you chose Debian, Ubuntu, Kubuntu, Edubuntu, or Mint, you're going to type the following:

sudo apt-get update
sudo apt-get upgrade

Learn these commands well; you will execute them routinely. Ideally, you should check for updates at least twice a week (at the minimum), in addition to any time you hear about a new exploit in a service that runs on your server.

Now, there are a lot of server security things I could mention here, but our focus is web application security. So the main things you'll want to keep updated are the first things we're going to install and configure:

  1. Your webserver software (Apache, nginx, lighttpd, etc.)
  2. Your scripting language interpreter (PHP, Perl, Python, Ruby, et al.)
  3. Your database software (MySQL, SQLite, PostgreSQL, etc.)
  4. Your Linux Kernel (Command: sudo apt-get dist-upgrade)

To do a basic Apache 2 + PHP5 + MySQL setup, you'll run shell commands that look like this:

sudo apt-get update
sudo apt-get install apache2 php5 mysql-client mysql-server php5-mysql openssl

To do a slightly more advanced nginx + php5-fpm + mysql setup, you'll do this:

sudo apt-get update
sudo apt-get install nginx php5-fpm mysql-client mysql-server php5-mysql openssl

It's worth pointing out that running PHP5 through FastCGI (which is usually managed with the Fastcgi Process Manager, or FPM) is how you run PHP5 on nginx properly. There is no mod_php for nginx.

At this point, you should run your operating system update/upgrade commands until you see something like this:

[email protected]:~# sudo apt-get upgrade
Reading package lists... Done
Building dependency tree
Reading state information... Done
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
[email protected]:~#

Getting the Ball Rolling

There are a lot of configuration options out there and I would deviate out of the scope of this article too far if I tried to cover every use case. So I'm going to take the liberty to just mention a few important things (some will be important later) and pass the torch to one of my favorite server security resources on the internet:

Structure your document roots to keep things hidden from users. If you want to keep all of your websites in /home/johndoe/www, then each webroot should be subdirectories including your main one.

[email protected]:~# ls -lah /home/johndoe/www
total 32K
drwxr-xr-x 5 root root 4.0K Jan 17 20:55 .
drwxr-xr-x 12 root root 4.0K Sep 28 01:20 ..
drwxr-xr-x 2 root root 4.0K Jan 17 20:55 mainsite
drwxr-xr-x 2 root root 4.0K Nov 13 06:13 includes
-rw-r--r-- 1 root root 15 Jan 18 03:03 internal404.html
drwxr-xr-x 2 root root 4.0K Nov 13 06:25 blog
-rw------- 1 root root 34 Dec 26 18:21 server.conf

In the above example, there are 3 directories (in blue): mainsite is where the main website is hosted; blog contains the blog subdomain of whatever website johndoe operates. The third one, includes, is where all of your PHP library files should be kept. There should be no way for a hacker to access this folder from their web browser.

Install an SSL/TLS Certificate and, if possible, force HTTPS with Hypertext Strict Transport Security (HSTS). This fixes a great deal many problems later on. You can get a free SSL/TLS certificate from StartCom at their website, Mixing HTTPS and HTTP is just as good as not using HTTPS at all. Use GlobalSign to detect common configuration weaknesses.

Disable directory indexes. If you need them, write your own using glob() and either a whitelist or a blacklist.

Uninstall any services you are not going to use. This should, in most cases, consist of:

  • A single webserver (nginx, apache2, lighttpd, etc.) on port 443 (and optionally 80)
  • A single mailserver (postfix, mailx, sendmail) on ports 465 and 995 (or 25 and 110 if you don't mind having hackers eavesdrop on all of your emails)
  • A single server-side programming language (PHP, Perl, Python, or Ruby being the most likely)

You could have a good reason to have gcc installed, but probably not netcat or firefox.

I highly recommend you read websites like Calomel and OWASP until your finger is raw from scrolling your mouse. Not for the damage to your finger, but because there's a vast wealth of knowledge and research into server security that will ultimately be to your benefit.

The Attacks

Above this point, everything we've done has been server and systems security. If you did it right, the web application part of things will be a cakewalk in comparison. From this point, the technologies I will employ are PHP (which includes editing php.ini) and MySQL. I'm going to assume a base familiarity with these technologies and the core concepts of programming.

Session Security

There are a couple of attacks that you can perform on user sessions:

  • Session fixation - In which you control (or predict) what another user chooses for their session ID
  • Session hijacking - Intercepting their session ID in some manner (cookie stealing, wiretapping, etc.)

Hijacking is a huge risk if you do not employ HTTPS (or employ it correctly). You should also make sure your session IDs are marked "HTTP only" and "Secure only", so modern web browsers will know to not expose their contents to Javascript or to send them over unencrypted HTTP. This can be accomplished by modifying the php.ini file or adding these statements to the top of your pages:

ini_set('session.cookie_httponly', true);
ini_set('session.cookie_secure', true);

To prevent session fixation, you're going to want to employ two main strategies: Using an entropy file, and a canary index.

Using an entropy file means making the following additions to your configuration or script:

ini_set('session.entropy_file', '/dev/urandom');
ini_set('session.entropy_length', '32');
ini_set('session.hash_function', 'sha256');
ini_set('session.hash_bits_per_character', '6');

This will use 32 bytes (256 bits) of pseudorandom data from your operating system's non-blocking random number generator, hash it with 256 bits, and produce a reasonable session ID (only 43 characters). A canary index means adding this sort of directive to your code:

if(empty($_SESSION['canary'])) {
    $_SESSION['canary'] = sha1($_SERVER['REMOTE_ADDR']);
if($_SESSION['canary'] != sha1($_SERVER['REMOTE_ADDR'])) {
    header("Location: /");
    die("Your IP address has changed. Logging you out now.");

When a session is created, the SHA1 hash of their IP address is stored secretly, and their ID is regenerated. In other words, if an attacker controlled the session ID, then there will be no canary index set in $_SESSION, so create a new ID. If the IP of the connecting computer changes, then destroy the session and log the user out. This means if an attacker creates a valid connection then sets the target's session ID to match his, the victim will get logged out. This isn't foolproof, but it raises the bar high enough to discourage this sort of attack. (It will also frustrate legitimate Tor users, so beware.)

To recap: HTTPS-only cookies, high-entropy session IDs, and canary indexes. Got it? Good. Let's move on.

Local and Remote File Inclusion

Local and Remote File Inclusion vulnerabilities happen in two instances. The first one is, you've left a PHP file on your server world-writeable and you have another script that can be tricked into writing to it. Then a hacker can go and access the file directly and it's all downhill from there.

The second is more common, and it looks something like this: because most people build their first websites like this:

    $page = isset($_GET['page'])?$_GET['page']:'index';

There are competing schools of thought on how to handle something like this. You have the input sanitation approach, which basically involves filtering forward-slashes and periods, and non-printable characters from the $page variable to prevent any sort of injection. You have the whitelist crowd, which uses a switch statement to only allow hard-coded values and default to a file. And finally you have the "don't build websites this way" crowd, which advocates not letting an attacker ever have any influence over require(), include(), fopen(), file_get_contents(), curl_*(), or any other function that can access the filesystem or the network.

I find the latter two categories to be the most effective. After all, plain filenames (such as /blog.php or and rewrite directives (such as /blog/post-title, which are used extensively in make for sexier URLs than /index.php?page=pleaseHackMe

Password Hashing

Current best standard: Use bcrypt with another hash algorithm on the inside, because bcrypt silently truncates passwords over a certain length.

A prediction on tomorrow's best standard: scrypt (which has an advantage over bcrypt in being memory-hard).

If you're using md5(), sha1(), a single pass of hash(), or you store your passwords unhashed, you are doing it wrong, and your users will suffer from your incompetence.

Securing File Uploads

A couple of things work here:

  1. Encrypt your files, store them in a random filename in a specific non-webroot directory, and use a database to keep track of their metadata (type, filename, size). Use a script to deliver them from the filesystem, and use header() calls and rewrite directives to trick browsers into treating them like images. Cache heavily to prevent resource exhaustion.
  2. Set up a second machine and use curl over HTTPS to transfer files to the second machine on your intranet, then configure your webserver to proxy requests to said webserver. That way, if an attacker manages to upload a reverse shell, it doesn't compromise your main webserver.
  3. Don't rely on the file extension or the user-provided $_FILES['abc']['type'] value. They can be spoofed.
  4. For images, use GD to read the image and write back out to a file of a different type. (Similar to the process of creating a thumbnail).

SQL Injection

Always sanitize your input:

  • mysql_real_escape_string()
  • preg_replace()
  • intval()

Consider switching to newer database APIs:

  • The MySQLi extension
  • PDO

This topic has been done to death. If you don't know how to stop an SQL injection, search everything above on the documentation and learn. Sanitize your input; use prepared statements if possible.

Sample code available here.

Cross-Site Scripting

The best solution I've found for XSS attacks is called HTMLPurifier. If you're outputting to an HTML field (i.e. <input value="" />), always wrap it in htmlentities() or htmlspecialchars() depending on your specific needs:

$out = htmlspecialchars($in, ENT_QUOTES | ENT_HTML5, 'UTF-8');

Make sure you escape quotes otherwise it's all for naught.

Cross-Site Request Forgery

Similar to canary indexes for session security, store pseudorandom data in a session variable and use hmac_hash() to produce a HMAC digest of said variable and the user's IP address. Put the hmac digest as a hidden value on the form, and when it arrives run the following checks in this order:

  1. Is the session variable defined?
  2. Does the HMAC match the hashed value of their IP and the session variable?

It also helps to use POST because it's harder to trick a browser into sending a POST than a GET.

Other Things of Importance

Check your logs daily. Not just for attempted security breaches, but for things breaking. If you can improve your PHP code to not break, when someone fuzzes their way in, it will be more conspicuous.

If you must rely on third-party software, always read the source code. If no source code is available, choose an alternative. Anything that a team of proprietary software developers can do, a team of free software developers can usually do better (they won't cut corners because it will be under public scrutiny).


Blog Archives Categories Latest Comments

Want to hire Scott Arciszewski as a technology consultant? Need help securing your applications? Need help with secure data encryption in PHP?

Contact Paragon Initiative Enterprises and request Scott be assigned to your project.