Thursday, July 29, 2010

Hardening PHP: How to securely include remote code (part 2)

In second post of the series I describe methods of checking the integrity of remote code - from checksums to (simple) Public Key Infrastructure. To transfer the code I introduce the popular Phar archives.

Checksums

In first post I've talked about the growing need of transferring and executing code from remote locations. I've also described why blindly trusting downloaded code is a security vulnerability, even if we download from a known location under our control.

To be sure that the downloaded code has not been modified (by an attacker) we must somehow validate it. The simplest form of validation is to compare a checksum of the code with a known value. Let's look at an example:

// checksum validation
$url = 'http://code.example.com/code.php';
$file = file_get_contents($url);
if (md5($file) == $code_checksum) {
    file_put_contents('code.php', $file);
    inlcude 'code.php';
}
We download the code over-the-wire, but before executing it, we calculate the MD5 sum of the file contents and compare it to our value. Checksum easily detects errors in file transfer. Also, if an attacker supplies his code, the checksum is different (let's ignore hash collisions for a moment) and the code would not execute. What is the problem? There are two:
  1. How should we get the checksums in the first place? If we have them locally - what is the point of not shipping the code with them anyway (minus licensing issues)? If we don't - we need to fetch them, so they could also be tampered with.
  2. The checksums are tied to a specific code version - if we update the remote code, we need to supply new checksums to all the clients (and do it securely).
Described issues are what is known in cryptography as the classic Key exchange dillemma.

Signing the code

Luckily, if we know upfront who will ship the code to our applications, these problems are solvable with the help of public/private key pair and code signing. Let's see how the code signing works:

The idea is simple - the producer (code author) generates two keys - a public and a private one. The keys work together like two sides of a coin. If one key is modified, the mathematical relationship between them breaks. When building the software, producer uses his private key and the code itself to generate a code signature - a special form of checksum. The checksum, together with code is then bundled together in a "signed code" package (think of a ZIP file).

Code verification does not use a private key, but a second key from the pair - the public one. The consumer checks the signature of the package and verifies it using producer's public key. If anyone modifies the code in the package - the signature will not match (that is the checksum functionality). But, even if the package has correct checksum but is signed with a different private key - it can be detected (because public and private keys are related!). The effect is that unless attacker has producer's private key - he cannot tamper with the code as it would fail the signature verification.

How to use that in our example?

  1. Generate the private and public key on a producer (it can also be a third-party server)
  2. Ship the producer's public key together with the clients' part of an application.
  3. Whenever the code to be downloaded is modified, sign it on producer
  4. Client downloads the new code and verifies it before executing (don't download the public key again! - it could be tampered with) 
Even if an attacker executes man-in-the-middle attack and ships the code instead of the producer server, without knowing the private key (which is securely stored on producer server and is never transferred) - his code will never be executed. We only execute code that is signed by private key paired with our known local public key.

Code signing in PHP world - Phar

Getting back to PHP, we have two problems to solve:
  1. What format of package to use? We cannot just append the signature after the code, as that would require stripping it manually before verification.
  2. How to perform the actual verification / signing process?
Luckily, there's Phar and it beautifully solves both issues. Phar is a container format (like .jar for Java) for PHP applications that also allows for signing and verifying signatures using OpenSSL public/private key pair.

Installing Phar

To use Phar for code signing, you need PHP compiled with --with-openssl switch. For PHP < 5.3, you also need to build the Phar (2.0.0+) extension from http://pecl.php.net.

E.g. under Ubuntu, these steps are required to build and configure the extension:
$ sudo apt-get install php5-dev
$ sudo pecl install pecl/phar
$ echo "extension=phar.so" | sudo tee /etc/php5/conf.d/phar.ini
$ echo "phar.readonly=0" | sudo tee -a /etc/php5/conf.d/phar.ini
The last line allows us to create Phar archives as by default only reading them is supported.

Creating keys

You also need to generate a public/private key pair - use these OpenSSL commands to do this:
#!/bin/bash
# priv.pem will contain your private key
# pub.pem will contain your public key
openssl genrsa -out priv.pem 1024
openssl rsa -in priv.pem -pubout -out pub.pem

Signing with Phar

Signing is dead easy (apart from errors in documentation :( )
$src = './src'; // source files to be built
$dest = './build/test.phar'; // phar destination file
$priv_file = './cert/priv.pem'; // path to PEM private file
$pub_file = './cert/pub.pem'; // path to PEM public file

$phar = new Phar($dest);
// get the private key
$private_key = file_get_contents($priv_file);
// apply the signature
$phar->setSignatureAlgorithm(Phar::OPENSSL, $private_key);
$phar->buildFromDirectory($src);
// attach the public key for verification
copy($pub_file, $dest . '.pubkey');
Phar requires the public key to be stored alongside phar archive in hardcoded location, so the last line copies our public key file next to it.

Verifying signature in Phar

To use Phar archives locally, you simply do:
require_once '/path/to/test.phar';

This will fetch the appropriate public key and perform the verification, throwing an Exception on error.

If you need to use the Phar archive remotely, you're out of luck though. For security reasons, Phar won't work with remote URIs (in other words, you cannot include a Phar archive remotely). Also the public key must be stored next to an archive in a predefined location. While Phar solves the signing problem, it does so for local includes only.

So, getting back to the question - how to securely include remote code? We need to find a way to ship .phar archives remotely AND verify the signatures without transferring the key, which will be covered in the last post from this series.

No comments: