yii2框架中存在的几条POP链学习

影响版本:Yii2 < 2.0.38

首先搜索反序列化起始点__destruct魔术方法,在结果的最后一行可以找到存在问题的__destruct

方法

\vendor\yiisoft\yii2\db\BatchQueryResult.php文件的79行的__destruct方法调用了reset方法,reset方法中存在的_dataReader值可控只需访问不存在close的类即可触发__call魔术方法

调用的利用点如图:

查找可以利用的__call魔术方法

在文件\vendor\fzaninotto\faker\src\Faker\Generator.php的283行调用了__call魔术方法

1
2
3
4
5
public function __call($method, $attributes)
{
return $this->format($method, $attributes);
}

调用了format方法在同文件的226行

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

这里会调用该文件236行的getFormatter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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中传入的参数不可控 所以需要找到一个可控参数的方法去执行我们的代码 查找全局可控的eval call_user_func 其中找到PHPUnti的eval 还有几个call_user_func 其中Delete Update 和View 这几个文件的方法需要传参

而Create Index则不需要 以Index为例子

\vendor\yiisoft\yii2\rest\IndexAction.php 76

1
2
3
4
5
6
7
8
public function run()
{
if ($this->checkAccess) {
call_user_func($this->checkAccess, $this->id);
}

return $this->prepareDataProvider();
}

checkAccess可控,id可控 即可控制checkAccess为我们的函数 id为我们的参数 即可实现RCE

POP2

在issue 有师傅发了另外两个pop链 https://github.com/yiisoft/yii2/issues/18293

其中一个在文件\vendor\codeception\codeception\ext\RunProcess.php 的98行stopProcess 方法中 其中他会遍历processes 数组并赋值给$process变量 然后进行$process->isRunning()

1
2
3
4
5
6
7
8
9
10
11
12
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 = [];
}

因为processes 可控 则 process可控当控制访问不存在isRunning()方法的类时 触发调用__call 魔术方法然后使用我们上面Faker\Generator.php 继续调用任意类中的任意方法 实现RCE

POP3

这个是调用yii组件Swift \vendor\swiftmailer\swiftmailer\lib\classes\Swift\KeyCache\DiskKeyCache.php 289行中存在的__destruct方法

1
2
3
4
5
6
public function __destruct()
{
foreach ($this->keys as $nsKey => $null) {
$this->clearAll($nsKey);
}
}

这里调用了225行的clearAll方法 其中keys可控

1
2
3
4
5
6
7
8
9
10
11
12
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]);
}
}

这里有一个is_dir的判断 path的值我们可控 如果控制为一个对象 则触发__toString方法

查找全局可以找到在文件\vendor\phpdocumentor\reflection-docblock\src\DocBlock\Tags\See.php 的79行

1
2
3
4
public function __toString() : string
{
return $this->refers . ($this->description ? ' ' . $this->description->render() : '');
}

这里面判断了description是否为空 不为空则进行$this->description->render() 因为description我们可控 则可以访问不存在render方法的类 触发__call魔术方法 接入第一条pop 的Faker\Generator的__call方法 然后CreateAction 中的run方法实现RCE