Spot The Bug challenge 2018 warm-up

Every now and then Securify publishes a Spot The Bug challenge to help people improve their bugfinding skills. Also, it's just fun to do. In this blog post you can find a warm-up up for the upcoming challenge! The code stems from a vulnerability that was encountered in the wild.

The code contains a vulnerability for bypassing an HMAC check. Note that this is a warm-up challenge and no prices are given for solutions (since it's given below).

The code

This script performs a DNS lookup for a host that a user provides. It uses an HMAC to make sure it is requested from a trusted source. Well implemented, right?

Vulnerability details

Let's take a walk through the code.

  • Line 3 to 6 require a user to submit an HMAC and a hostname.
  • A secret value is taken from an environment variable.
  • If a user sets a nonce, the secret is updated using secret = HMAC(SHA256, nonce, secret).
  • An HMAC is generated as HMAC = HMAC(SHA256, hostname, secret).
  • The generated HMAC is compared to the supplied HMAC.

Let's take a moment and remember two wise quotes from the crypto world: "Complexity is the worst enemy of security" and "Never roll your own crypto". With that, we can quickly see what went wrong in this setup.

If the programmer would have skipped step 3, and just created an HMAC using the supplied hostname and the secret, there wouldn't be a problem. A nonce is normally used in cryptography to prevent repeating messages, or to generate a unique key. For example, it can be used to prevent the same plaintext from generating the same ciphertext. However, here we see a custom implementation, where the secret is updated with an HMAC of the nonce. Theoretically this shouldn't be directly exploitable. But in the complex world of PHP, this led to a critical security vulnerability.

To get right to the vulnerability, we can observe the behavior or the hash_hmac function when an array is supplied as a message. Let's run the following code locally:

$hmac = hash_hmac('sha256', Array(), "SecretKey");
echo $hmac == false;
$ php foo.php
Warning: hash_hmac() expects parameter 2 to be string, array given in /Users/stb/stb-test.php on line 3
1

Here we observe that hash_hmac just triggers a warning and returns false. This can be triggered in the original code by supplying an array as a hostname. However, this would not bypass the HMAC check because strict comparison is used (!==).

if ($hmac !== $_POST['hmac']) {

Because strict comparison compares data types, the value false won't compare to a string like "false", like with loose comparison. And because $_POST['hmac'] can't be false, this is not the way to go.

Our programmer decided to add extra complexity by adding a nonce to the equation, for whatever reason.

if (isset($_POST['nonce']))
	$secret = hash_hmac('sha256', $_POST['nonce'], $secret);

What happens when we supply an array for the nonce value? Well, hash_hmac will return false. Then, the new HMAC will be generated in the form of HMAC = hash_hmac(SHA256, $_POST['host'], false)! An attacker can now generate a valid HMAC for any message. For example:

hash_hmac('sha256', "securify.nl", false) = c8ef9458af67da9c9086078ad3acc8ae71713af4e27d35fd8d02d0078f7ca3f5

The URL for exploiting the vulnerability then looks like:

?nonce[]=&hostname=securify.nl&hmac=c8ef9458af67da9c9086078ad3acc8ae71713af4e27d35fd8d02d0078f7ca3f5

Of course, there is code execution possible with the hostname parameter, so from here on out it's game over.

Spot The Bug 2018

We expect to launch a Spot the Bug challenge this year at the start of Q2. If you like this challenge you can subscribe by sending an email to stb@securify.nl, and we'll keep you up to date.

Questions or feedback?