不安全反序列化利用篇(三):从对象字段到危险方法调用
本文中的实验均基于 PortSwigger Web Security Academy 官方教学靶场仅针对指定 Lab 页面进行授权测试目的是完成指定 Lab不涉及真实网站或第三方系统。前两章主要讨论反序列化对象如何影响认证和授权判断修改对象属性可以改变权限状态修改数据类型可以影响认证比较。第三章开始利用思路进一步扩展反序列化对象中的数据不一定只会被拿来判断它还可能被应用功能当作参数传入文件删除、文件读取、文件写入、模板处理等危险方法。这类问题的核心不是攻击者直接调用了危险函数而是攻击者控制了反序列化对象中的某个字段并让应用在正常执行路径中使用这个字段。换句话说攻击者并不需要“发明”新的代码路径只需要让已有功能处理恶意数据。本章通过两个 PortSwigger Lab 作为例子分析两种递进关系利用应用已有功能 ↓ 手动触发危险方法 利用 magic method ↓ 自动触发危险方法两个实验的目标都是删除 Carlos home directory 下的morale.txt文件。这个目标只是结果验证真正要理解的是反序列化对象中的可控数据如何一步步流入危险方法。一、从安全判断到危险方法调用前面的反序列化利用主要围绕“判断逻辑”展开。例如if ($user-isAdmin) { // allow admin access }或者if ($login[access_token] $storedToken) { // authenticated }这类问题的本质是服务端把反序列化对象中的字段当作认证或授权依据导致攻击者可以通过修改字段影响判断结果。第三章关注的是另一类场景服务端不是拿字段做判断而是把字段作为危险方法的输入。例如unlink($user-avatar_link);在这种情况下字段值不再只是影响if分支而是直接决定危险操作作用于哪里。只要攻击者能控制这个字段就可能让应用删除、读取或写入非预期文件。可以把这一类攻击链抽象为客户端可控的序列化对象 ↓ 服务端反序列化对象 ↓ 对象字段被应用功能读取 ↓ 字段值进入危险方法 ↓ 应用执行非预期操作所以分析反序列化漏洞时不能只找isAdmin、role、access_token这类认证授权字段也要关注文件路径、模板路径、URL、命令参数、回调函数、类名等可能进入危险方法的数据。二、利用应用功能让正常功能处理恶意字段第一个实验Using application functionality to exploit insecure deserialization展示的是手动触发型利用。实验中的应用使用基于序列化对象的 Session 机制。用户对象中存在一个头像路径字段例如avatar_link users/wiener/avatar正常情况下这个字段表示当前用户头像文件的位置。删除账号时应用可能会顺便删除这个头像文件。后端逻辑可以抽象为$user unserialize($_COOKIE[session]); deleteUser($user-username); unlink($user-avatar_link);如果avatar_link来自客户端可控的序列化对象攻击者就可以把它从自己的头像路径改成目标文件路径/home/carlos/morale.txt随后攻击者触发“删除账号”这个正常功能。服务端以为自己在删除当前用户的头像文件实际上删除的是攻击者指定的目标文件。这个实验的利用链可以概括为登录普通用户 ↓ 解码 session cookie ↓ 定位文件路径字段 avatar_link ↓ 将路径改为 /home/carlos/morale.txt ↓ 保持序列化格式合法 ↓ 触发删除账号功能 ↓ 服务端调用文件删除方法 ↓ 目标文件被删除这里的重点不是“删除账号”按钮本身而是这个按钮背后的业务逻辑读取了反序列化对象中的路径字段并将其传入了危险方法。攻击者借用的是应用已有功能而不是直接访问底层文件系统。三、路径字段为什么敏感文件路径字段在反序列化对象中非常敏感因为它一旦可控后续影响取决于它进入了什么方法。例如unlink() → 文件删除 file_get_contents() → 文件读取 file_put_contents() → 文件写入 include() / require() → 文件包含在第一个实验中路径字段进入的是unlink()因此造成的是任意文件删除。攻击者没有直接调用unlink()而是让应用在删除账号时自动执行unlink(/home/carlos/morale.txt);这说明反序列化漏洞的影响不只取决于对象里有哪些字段还取决于这些字段后续被哪些功能使用。同一个字段在不同上下文里可能产生完全不同的安全影响头像路径用于页面展示 → 可能只是图片加载异常 头像路径用于文件读取 → 可能造成任意文件读取 头像路径用于文件删除 → 可能造成任意文件删除 头像路径用于模板处理 → 可能进入更复杂的执行链因此分析这类漏洞时不能停在“字段可以修改”还要继续追踪“字段被谁使用、如何使用、是否进入危险方法”。四、Magic method从手动触发到自动触发第二个实验Arbitrary object injection in PHP引入了 magic method。它的意义在于攻击者不一定需要手动点击某个业务功能只要服务端反序列化恶意对象某些方法就可能自动执行。Magic method 是面向对象语言中的特殊方法不需要开发者显式调用。当特定事件发生时语言运行时会自动调用它。PHP 中常见的 magic method 包括__construct() __wakeup() __destruct() __toString()其中__wakeup()会在unserialize()恢复对象时自动触发__destruct()会在对象销毁时自动触发。它们本身不是漏洞危险点在于这些方法内部是否处理了攻击者可控的数据并将这些数据传入危险操作。第二个实验中通过源码备份文件可以看到CustomTemplate类。关键逻辑可以抽象为class CustomTemplate { private $lock_file_path; function __destruct() { if (file_exists($this-lock_file_path)) { unlink($this-lock_file_path); } } }这里的危险点是unlink($this-lock_file_path);只要攻击者能让服务端反序列化出一个CustomTemplate对象并控制它的lock_file_path属性那么对象销毁时__destruct()就会自动调用unlink()删除指定文件。攻击链可以概括为获取源码 ↓ 发现 CustomTemplate 类 ↓ 发现 __destruct() magic method ↓ 发现 lock_file_path 会进入 unlink() ↓ 构造 CustomTemplate 序列化对象 ↓ 将 lock_file_path 指向 /home/carlos/morale.txt ↓ 替换 session cookie ↓ 服务端 unserialize() ↓ 对象销毁时自动触发 __destruct() ↓ 目标文件被删除这个实验的关键变化是危险方法不再由删除账号功能触发而是由对象生命周期自动触发。五、任意对象注入的意义前面的实验通常是在修改服务端原本给出的对象例如User对象。而 arbitrary object injection 的关键是如果服务端没有限制反序列化对象的类型攻击者不一定只能提交User还可以提交应用中任意可用类的对象。服务端原本可能期待O:4:User:...攻击者却提交O:14:CustomTemplate:...只要CustomTemplate类在应用代码中存在PHP 就可能实例化这个对象。即使后续业务逻辑发现“这不是合法用户”并抛出Invalid user恶意对象也可能已经被创建magic method 也可能已经在对象生命周期中触发。这里要分清两层逻辑反序列化层 对象是否成功被创建 业务校验层 应用是否接受它作为合法用户Arbitrary object injection 利用的是第一层。只要对象成功创建并且 magic method 能够执行后续业务逻辑报错不一定阻止利用成立。这也是为什么实验中Invalid user不一定代表失败。它说明应用业务逻辑拒绝了非预期对象但危险代码可能已经在此之前或请求结束时执行。六、这两个实验的递进关系这两个实验展示的是同一类问题的两个阶段。第一个实验是手动触发控制对象字段 ↓ 手动调用应用功能 ↓ 应用功能使用字段执行危险操作第二个实验是自动触发注入非预期对象 ↓ 反序列化或对象销毁自动调用 magic method ↓ magic method 使用属性执行危险操作它们的共同点是危险操作不是攻击者直接调用的而是应用代码自己调用的。攻击者真正控制的是数据以及数据进入代码路径的方式。区别在于触发点不同Using application functionality 需要攻击者访问某个正常功能例如删除账号。 Arbitrary object injection 只要服务端反序列化恶意对象magic method 就可能自动触发。这一步是反序列化利用从基础到高级的关键转折攻击者不再只修改已有对象也不再只等待某个业务功能使用字段而是开始主动选择应用中可用的类并利用类中的自动执行方法。七、Gadget、Magic method 和 Sink 的关系这一章已经开始接近 Gadget Chain 的基础概念所以需要把几个术语放清楚。Gadget 是应用或依赖库中已经存在、可以被攻击者在非预期上下文中利用的一段代码。攻击者没有上传新代码而是复用目标系统已有代码。Magic method 经常可以作为反序列化攻击中的入口 Gadget。因为它会在反序列化、对象销毁、字符串转换等事件中自动执行不需要攻击者显式调用。Sink 是最终执行敏感操作的位置例如unlink() file_get_contents() file_put_contents() include() exec() system()第二个实验中的链路很短不可信序列化数据 ↓ CustomTemplate 对象被反序列化 ↓ __destruct() 自动触发 ↓ lock_file_path 进入 unlink() ↓ 删除目标文件在这个链路中CustomTemplate::__destruct() 是 Gadget unlink() 是 Sink lock_file_path 是攻击者控制的数据如果一个 Gadget 就能把可控数据直接送进 Sink利用链会很短。后续更复杂的反序列化攻击中往往需要多个 Gadget 串联让数据经过多个类和方法最终到达 Sink这就是 Gadget Chain。八、失败时如何定位问题这两个实验很容易因为序列化格式、编码或对象属性可见性出错。排查时应按攻击链分层定位。如果出现unserialize() failed说明 PHP 在反序列化阶段失败。常见原因是序列化格式错误、字符串长度不匹配、Base64 或 URL 编码损坏或者 private 属性中的 null byte 没有正确构造。如果出现Invalid user说明对象已经成功反序列化但业务逻辑发现它不是预期的User对象。这个错误不一定代表攻击失败因为 magic method 可能已经在对象创建后或请求结束时执行。如果对象能反序列化但目标文件没有被删除可能是属性没有真正写入目标字段。例如 PHP private 属性在序列化中有特殊格式不能简单写成普通属性名。private 属性名在序列化数据中通常包含类名和 null byte如果构造不正确类内部访问的$this-lock_file_path就不会指向攻击者设置的路径。因此排错时可以按下面顺序看序列化格式是否能被解析 ↓ 对象类型是否是目标类 ↓ 属性是否写入 magic method 实际读取的位置 ↓ magic method 是否会自动触发 ↓ 可控属性是否进入危险 Sink不同报错代表攻击链断在不同位置。不要把所有失败都归结为 payload 错也不要把业务层报错直接等同于反序列化失败。九、防御思路防御这类反序列化问题核心原则仍然是不要对不可信数据执行通用反序列化更不能让反序列化对象直接进入危险功能。更合理的做法包括不把完整对象序列化后交给客户端保存客户端只保存不可预测的 Session ID真实状态保存在服务端如果必须使用客户端状态应使用服务端密钥进行签名或 MAC 校验反序列化前限制允许的类避免 arbitrary object injection对反序列化后的字段进行严格类型、格式和值域校验不让客户端可控字段直接进入文件路径、命令、模板、URL 等危险参数对文件操作使用固定目录、路径白名单和路径规范化校验审计__wakeup()、__destruct()、__toString()等 magic method避免在 magic method 中处理不可信数据或调用危险方法。签名可以防止客户端直接篡改对象但不能替代类白名单、危险方法审计和服务端权限设计。一旦签名校验遗漏、密钥泄露或者存在其他反序列化入口magic method 和 Gadget Chain 仍可能成为攻击路径。十、小结第三章的重点是反序列化对象中的字段不只会影响判断逻辑也可能成为应用功能或 magic method 的危险参数。Using application functionality to exploit insecure deserialization说明如果应用的正常功能会读取反序列化对象中的路径字段并将其传入文件删除方法攻击者就可以通过修改该字段借应用功能删除非预期文件。Arbitrary object injection in PHP进一步说明如果服务端没有限制反序列化对象的类型攻击者可以注入应用中任意可用类的对象。一旦该类包含会自动触发的 magic method并且 magic method 将可控属性传入危险 Sink攻击者就能在不手动调用业务功能的情况下触发危险操作。这一章可以用一条主线概括客户端可控序列化数据 ↓ 服务端反序列化对象 ↓ 对象字段或属性进入应用代码 ↓ 应用功能或 magic method 被触发 ↓ 可控数据流向危险 Sink ↓ 产生文件删除等安全影响前两章关注的是“反序列化对象如何改变认证和授权判断”。第三章关注的是“反序列化对象如何驱动已有代码执行危险操作”。这一步是理解 Gadget Chain 的前置基础攻击者真正利用的不是凭空出现的新代码而是目标应用中已经存在、并且能被反序列化数据影响的代码路径。