PHP反序列化中“控”魔术方法本质是通过可控序列化字符串精准控制对象属性值,使__wakeup()、__destruct()、__toString()等自动触发的魔术方法执行危险操作;关键在于属性值能否成为system()、eval()等函数的参数,并形成自然调用链。

PHP反序列化中“控”魔术方法,本质不是直接控制方法本身,而是通过可控的序列化字符串,精准控制对象的属性值,从而在魔术方法自动触发时,让其中的危险逻辑(如system()、file_get_contents()、eval())执行你指定的操作。
关键不在于“调用哪个方法”,而在于:
✅ 你能否把恶意数据塞进对象的某个属性里;
✅ 这个属性是否会被某个魔术方法读取并传给危险函数;
✅ 整个调用链是否能在反序列化后自然走通。
哪些魔术方法最常被“控”?
-
__wakeup():反序列化完成后立刻触发,适合初始化或预处理。若里面用了$this->xxx去拼接路径、命令或文件名,就容易被控。 -
__destruct():脚本结束或对象销毁时触发,隐蔽性强,常用于“收尾式”RCE或文件操作。 -
__toString():对象被当作字符串使用时(比如echo $obj、"result: ".$obj)触发,适合配合preg_match、print_r等上下文利用。 -
__invoke():当对象被当成函数调用($obj())时触发,需业务中存在类似调用逻辑。 -
__call()/__get()/__set():在访问不存在的方法或属性时触发,适合构造间接调用链(POP链)。
注意:
__construct()在反序列化时不执行;__sleep()只在serialize()时触发,和反序列化无关。
怎么实现“控”?三步到位
-
第一步:找到可利用的类和魔术方法
审计代码,定位定义了__wakeup、__destruct等方法的类,尤其关注方法体内是否包含:-
system($this->cmd) -
include($this->file) -
file_get_contents($this->path) -
eval($this->code) call_user_func([$this, $this->method])
-
-
第二步:确认属性可控且类型匹配
检查该类的属性是否为public(最容易控制),或虽为protected/private但序列化格式可手工构造(如protected属性序列化为"\0*\0attr",private为"\0ClassName\0attr")。
例如:class Logger { public $logFile = '/tmp/log.txt'; public function __destruct() { file_put_contents($this->logFile, 'pwned'); } }攻击者只需把
$logFile改成/var/www/html/shell.php,就能写入WebShell。 -
第三步:构造合法序列化字符串并传入
手动修改serialize()输出,或用脚本生成:$payload = 'O:6:"Logger":1:{s:7:"logFile";s:23:"/var/www/html/shell.php";}'; unserialize($payload);如果属性是
*protected,需注意编码格式:s:7:"logFile"→s:13:"<p>如果属性是<code>protected,需注意编码格式:s:7:"logFile"→s:13:"\0*\0logFile"(*是ASCII 0x00,PHP中用\000表示)如果属性是
logFile"(protected,需注意编码格式:s:7:"logFile"→s:13:"\0*\0logFile"(*是ASCII 0x00,PHP中用\000表示)*是ASCII 0x00,PHP中用<p>如果属性是<code>protected,需注意编码格式:s:7:"logFile"→s:13:"\0*\0logFile"(*是ASCII 0x00,PHP中用\000表示)0表示)立即学习“PHP免费学习笔记(深入)”;
常见绕过技巧(辅助“控”得更稳)
绕过
__wakeup()限制:PHP 5.6.25 及之前、7.0.10 及之前,若序列化字符串中声明的属性个数 > 实际个数,__wakeup()会被跳过。
如原类有2个属性,把O:4:"Cat":2:{...}改成O:4:"Cat":3:{...}即可绕过exit()类防护。利用原生类构造POP链:当业务类没危险操作时,可串联PHP内置类(如
GuzzleHttp\Cookie\FileCookieJar、Monolog\Handler\StreamHandler)形成调用链,最终触达system或file_put_contents。PHAR协议触发:把恶意序列化数据打包进
phar://xxx.jpg,再通过file_exists()、class_exists()等函数触发反序列化——此时unserialize()没显式出现,但底层仍会执行。
不复杂但容易忽略:真正决定能不能“控”的,从来不是魔术方法有多少个,而是你塞进去的属性值,能不能在方法执行时,恰好成为危险函数的参数。



















