In February 2019, CVE-2019-6340 was released, which disclosed a bug in the RESTful web services module of Drupal. This bug can be exploited to perform RCE. RCE is only possible if the Drupal installation has all the web services installed (HAL, Serialization, RESTful Web Services, and HTTP Basic Authentication, shown in the following screenshot):
The RESTful Web Services module communicates with Drupal using REST APIs, which can perform operations such as update, read, and write on website resources. It depends on the serialization module for the serialization of data that is sent to and from the API. Drupal 8 Core uses the Hypertext Application Language (HAL) module, which serializes entities using HAL when enabled. We can check whether a Drupal server has these web services enabled by requesting a node using the GET method with the _format=hal_json parameter, as can be seen in the following screenshot:
If the modules are installed, then we'll get a JSON-based response, as shown here:
If the server does not have the web service modules, we'll get a 406 (Not Acceptable) HTTP code error:
This vulnerability exists because the LinkItem class takes unsanitized user input and passes it to the unserialize() function:
As we can see in the following screenshot, according to the PHP manual for the unserialize() function, when using unserialize(), we should not let untrusted user input be passed to this function:
In order to exploit this vulnerability, three conditions should be satisfied:
- The application should have an unserialize() function that can be controlled by us.
- The application must have a class that implements a PHP magic method (destruct() or wakeup()) that carries out dangerous statements.
- There needs to be a serialized payload that uses the classes loaded in the application.
From the previous screenshot, we can confirm that we have control over the $value['options'] form entity. To check for the magic methods, let's search for the destruct() function within the source code using the following command:
ag __destruct | grep guzzlehttp
The following screenshot shows the output of the preceding command:
Note: You have to install the ag package before executing the preceding command.
In the preceding screenshot, we grepped out guzzlehttp because Guzzle is used by Drupal 8 as a PHP HTTP client and framework for building RESTful web service clients.
From looking at the FnStream.php file (refer to the preceding screenshot), we can see that the __destruct() magic method is calling the call_user_func() function, as shown in the following screenshot:
call_user_func() is quite a dangerous function to use, especially when more than one argument is passed. We can use this function to perform a function injection attack:
According to OWASP, a function injection attack consists of the insertion or injection of a function name from the client into an application. A successful function injection exploit can execute any built-in or user-defined function. Function injection attacks are a type of injection attack in which arbitrary function names, sometimes with parameters, are injected into an application and executed. If parameters are passed to the injected function, this leads to RCE.
According to the Drupal API documentation, the LinkItem class is used to implement the link field type:
We know that the LinkItem class passes unsanitized user input to the unserialize() function, but to invoke this class, we need to invoke an entity first. An entity would be one instance of a particular entity type, such as a comment, a taxonomy term, or a user profile, or a bundle of instances, such as a blog post, article, or product. We need to find an entity that is used by LinkItem for navigation. Let's search for an entity in the source code using the following command:
ag LinkItem | grep Entity
The following screenshot shows the output of the preceding command:
As we can see from the preceding screenshot, LinkItem is used to navigate to the MenuLinkContent.php and Shortcut.php entities and, as we can see from the Shortcut.php file, the shortcut entity is creating a link property:
To trigger the unserialize() function, we need to align together all the elements that we have explained so far:
{ "link": [ { "value": "link", "options": "<SERIALIZED_PAYLOAD>" } ], "_links": { "type": { "href": "localhost/rest/type/shortcut/default" } } }
Now that we have met two out of the three conditions, the only thing left to do is to create our serialized payload. There are various ways to create a serialized payload, but we will use a library known as PHP Generic Gadget Chains (PHPGGC) to create a serialized payload for Guzzle. To generate a serialized payload using phpggc, we use the following command:
./phpggc <gadget chain> <function> <command> --json
The following screenshot shows the output of the preceding command:
The JSON serialized payload generated in the preceding screenshot will call the system() function and run the id command. We will submit the entire payload with a GET/POST/PUT method in the following URL format: localhost/node/1?_format=hal_json
The server will execute the id command and return us the output shown here:
We have successfully achieved the RCE, but the question still remains: why did the serialized payload work? To answer this question, we need to understand what general serialized data looks like and learn about serialized formats.