Securing a PHP Application in 2016: The Pocket Guide

It was the year 2016. A business owner just had a great idea to expand their reach in the marketplace, and they needed to build a web application to pull it off. After a bit of market research, they decide the best course of action is to hire PHP programmers to build their vision.

Let's say you're a PHP programmer. A few days later, you find yourself employed at the company to make this vision happen. As you're sitting at the desk reviewing the wireframes that were agreed upon, the news on TV says something that catches your ear. You turn the volume up and learn that another company got hacked, all their customers' data was compromised, and it's just a mess all around.

You look back at your wireframes and wonder, "If it were to happen to this project, how would it fare?" How would you even begin to answer this question?

Understanding the Essence of Security

Please set aside most of what you've heard over the years; chances are, most of it just muddies the water. Security is not a product. Security is not a checklist. Security is not an absolute.

Security is a process. Security is an emergent property of a mature mindset in the face of risk.

Perfect security is not possible, but attackers do have budgets. If you raise the cost of attacking a system (your application or the networking infrastructure it depends on) so high that the entities that would be interested in defeating your security are incredibly unlikely to succeed, you'll be incredibly unlikely to be compromised.

Even better, the most effective ways to raise the cost of attack against your system don't significantly increase the cost of using the system legitimately. This is no accident; as Avi Douglen says, "Security at the expense of usability, comes at the expense of security."

With that in mind, let's look at some easy security wins you can implement in your PHP applications that significantly raise the cost of attack without making your software unusable.

Improve PHP Security in 4 Easy Steps

Use PHP 7 in All New Development

PHP 7 does three important things that significantly improve the security of software written in it:

PHP 7 dropped support for the old mysql_* functions, which a lot of outdated and insecure PHP tutorials used. PHP 7 allows you to add scalar type declarations to your code. PHP 7 ships with a secure random number generator.

Let's look at an example of the first two items in that list.

<?php /** PHP 5 snippet */ class Foo { public function get_products($name, $start = 0, $end = 30) { $name = mysql_real_escape_string($name); $query = mysql_query("SELECT * FROM products WHERE name LIKE '%{$name}%' LIMIT {$start}, {$end}"); $products = []; while ($row = mysql_fetch_assoc($query)) { $products []= $row; } return $products; } } $foo = new Foo; $products = $foo->get_products($_GET['search']);

This appears secure (we're escaping strings), but what if you accidentally passed a GET parameter as the second and third arguments to $foo->get_products() ? Chances are, SQL injection would be the result.

Now let's look at the PHP 7 approach:

<?php declare(strict_types=1); /** PHP 7 snippet */ class Foo { protected $pdo; public function __construct(PDO $pdo) { $this->pdo = $pdo; $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } public function get_products(string $name, int $start = 0, int $end = 30): array { $stmt = $this->pdo->prepare( "SELECT * FROM products WHERE name LIKE :name LIMIT {$start}, {$end}" ); $stmt->execute([ 'name' => '%' . $name . '%' ]); return $stmt->fetchAll(PDO::FETCH_ASSOC); } } $foo = new Foo(new PDO(/* ... */)); $products = $foo->get_products($_GET['search'] ?? '');

Is this more secure than the PHP 5 snippet? Certainly. If a developer accidentally passes a GET parameter to the second or third arguments, this will throw a TypeError unless they cast the user's input to an integer first. If the script doesn't catch the TypeError , then the script aborts execution. Type safety is useful for stopping unintended consequences.

There are no known integer inputs that can result in SQL injection, given the code above. However, it's still highly recommended to avoid string concatenation in SQL queries at all.

<?php declare(strict_types=1); /** * Better PHP 7 snippet. If you're going to reference anything for * your own development, this is the snippet you want. */ class Foo { /** * @var \PDO $pdo */ protected $pdo; /** * @param PDO $pdo */ public function __construct(PDO $pdo) { $this->pdo = $pdo; $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } /** * Get a list of products, with pagination support. * * Note that this LIMIT syntax is almost exclusive to MySQL. For PostgreSQL, * you'll want to use "OFFSET :start LIMIT :end" * * @param string $name * @param int $start * @param int $end * @return array */ public function get_products(string $name, int $start = 0, int $end = 30): array { $stmt = $this->pdo->prepare( "SELECT * FROM products WHERE name LIKE :name LIMIT :start, :end" ); $stmt->execute([ 'name' => '%' . $name . '%', 'start' => $start, 'end' => $end ]); return $stmt->fetchAll(PDO::FETCH_ASSOC); } } $foo = new Foo(new PDO(/* ... */)); $products = $foo->get_products($_GET['search'] ?? '');

Furthermore, both the MySQLi and PDO extensions support a feature called prepared statements. The old MySQL extension did not.

PHP 7 marked an inflection point in the PHP language (and the ecosystem surrounding it) to make it easier to do the secure thing than the insecure thing.

Use HTTPS Everywhere

A website that uses HTTPS properly almost never has to worry about session hijacking attacks (for example, Firesheep); and thanks to Let's Encrypt and open source projects like Caddy - a webserver that offers automatic free HTTPS, it's never been easier to adopt HTTPS.

Compare, for instance, this Caddyfile for our project, CMS Airship with its congruent Apache or nginx configurations.

Even though modern HTTPS (the TLS 1.2 variety) adds a layer of authenticated encryption to your network traffic, its CPU and memory usage overhead has been proven negligible. With HTTP/2, it's often faster for your users to connect over HTTPS than it is over HTTP.

For marketing incentive, HTTPS is also a boon for search engine optimization.

Use Security Headers

Security expert Scott Helme has put together a website that allows you to assess your usage of HTTP response headers that enable and/or configure security features, at SecurityHeaders.io. (You can see our scan results here.)

Some security headers are trivial to implement ( header("X-Frame-Options: SAMEORIGIN"); makes clickjacking nigh-impossible while allowing the use of HTML frames if your website was designed to use them).

For the more complicated security headers, Paragon Initiative Enterprises has developed open source software to make their implementation easier. Consider, for example, CSP Builder (which assembles Content-Security-Policy headers for you).

<?php use ParagonIE\CSPBuilder\CSPBuilder; (new CSPBuilder()) ->addSource('image', 'https://ytimg.com') ->addSource('frame', 'https://youtube.com') ->addSource('script', 'https://www.google.com') ->addDirective('upgrade-insecure-requests', true) ->sendCSPHeader();

At bare minimum, you should send these headers:

Use Trustworthy Reference Material

Although adopting PHP 7 means you'll miss out on most of the ways PHP developers shoot themselves in the foot while building software, there are still tutorials being written in 2016 that contain ill-advised (or outright vulnerable) practices. A great way to avoid accidentally trusting an unreliable tutorial is to proactively learn the best practices.

Start with our Gentle Introduction to Application Security, then check out our application security reading list. Most of the concepts we emphasize in our blog posts are also widely applicable outside PHP security.

Taking PHP Security to the Next Level

If you follow the four easy steps above, you'll be in much better shape when it comes to defending against computer criminals.

Earlier we had said, "If you raise the cost of attacking a system (your application or the networking infrastructure it depends on) so high that the entities that would be interested in defeating your security are incredibly unlikely to succeed, you'll be incredibly unlikely to be compromised." There's a corollary that must be stated here.

You cannot know everyone that is interested in attacking your system. You similarly cannot know why every possible attacker in the universe may want to attack your system.

There are a lot of companies that try to fill in this gap. They emphasize attribution, threat intelligence, and real-time attack monitoring. You can, for the most part, safely ignore all of these things they are selling.

Consider this: Even if you were to succeed in knowing who is attacking your network and what they hope to gain, the most you can glean from this is psychological comfort in knowing your enemy and maybe retributive prosecution (if they're in another country, the odds become much slimmer).

If your goal is to write secure PHP applications:

You generally don't need to know who the attacker is.

the attacker is. You generally don't need to know why they want to break in.

Conversely:

You should know what attacks are possible.

attacks are possible. You should know where attacks should come from.

attacks should come from. If an incident does occur, you should know when and how it happened, so you can prevent it (and similar attacks) from happening again.

If you find yourself asking, "Who would want to attack us? It's not worth it for them to target such small fry," consider the current trend of computer crime: ransomware and watering-hole attacks on ad networks are quickly becoming the biggest threats to the security of most peoples' computers.

Your system might not be the end-game, especially if the attacker is sophisticated. Always look to improve. Security is a process. It's not a destination.

If you run out of ideas to improve, consider hiring a security company to perform a comprehensive penetration test and/or audit your source code. Specialists will generally have deeper insight into the more advanced attacks that are possible as well as the best defense against them.

(Full disclosure: Our company specializes in developing secure PHP software; penetration testing and source code auditing are both services we provide our clients.)