php反序列化和序列化
-
PHP序列化:serialize()
序列化是将变量或对象转换成字符串的过程,用于存储或传递 PHP 的值的过程中,同时不丢失其类型和结构。“序列化”是一种把对象的状态转化成字节流的机制
类似于这样的结构:
O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}
对象类型:名称长度:对象名称:对象个数:{属性类型:属性长度:属性名称;内容类型:内容长度:内容;}
;是用来分隔属性的
- PHP反序列化:unserialize()
反序列化是将字符串转换成变量或对象的过程,“反序列”是把序列化成的字节流用来在内存中重新创建一个实际的对象。这个机制被用来“持久化”对象
- 二者关系
(这里参考大佬的文章在这里说明一下,引用的文章在末尾会提到)
对象被转换成“字节流”后可以存入文件,内存,或者是数据库内进行持久化保存。然后通过“反序列化”可以把“字节流”转换成实际的对象
反序列化字符逃逸
(这里在网上看到了很多大佬的文章,其实讲的我都有点看不太懂,最后在准备放弃的时候,看到了路另外一个大佬写的文章,这里给大家分享一下,接下来我的实践部分也是基于大佬的文章,大佬文章献上:PHP反序列化字符逃逸详解_php filter字符串溢出-CSDN博客 )
其实php反序列化本质就是改变序列化字符串的长度,导致反序列化漏洞
该漏洞利用的是:反序列化的过程是有一定识别范围的,在这个范围之外的字符(如花括号外的字符串)都会被忽略,不影响反序列化的正常进行
就例如,
$str=O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}
$str=O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";} abcd
两个的执行结果是一样的
反序列化字符逃逸类型的题有哪些共同点:
- php序列化后的字符串经过了替换或者修改,导致字符串长度发生变化。(如filter函数)
- 总是先进行序列化,再进行替换修改操作。
一般有两种情况,第一种:替换修改后导致序列化字符串变长
第二种:替换修改后导致序列化字符串变短
替换修改后导致序列化字符串变长:
代码如下
这段代码的意思是,将$str参数里面的bb替换成ccc,然后将name赋值为aaaa,pass赋值为123456,两个设置为公有属性,然后进行反序列化,输出pass变量
我们想要的是如何在不直接修改pass的值的条件下,间接修改输出的pass的值
代码如下
我们可以看到,反序列化字符串都是以一";}
结束的,所以如果我们把";}
带入需要反序列化的字符串中(除了结尾处),就能让反序列化提前闭合结束,后面的内容就会被丢弃(利用的就是php反序列化识别有限的条件)
在上面的例子中,123456就是被丢弃的值
在反序列化的时候php会根据s所指定的字符长度去读取后边的字符。如果指定的长度s错误则反序列化就会失败
这里用大佬的图
此时的name所读取的数据为aaaa"
而正常的语法是需要用";
去闭合当前的变量,而因为长度错误所以此时php把闭合的双引号当做了字符串,所以下一个字符就成了分号,没能闭合导致抛出了错误
如果我们将name变量中添加bb则程序就会报错,因为bb将被filter函数替换成ccc,ccc的长度比bb多1,这样前面的s所代表的长度为6但是内容却变长了,成了ccc。
可见在序列化后的字符串在经过filter函数过滤前,s为6,内容为aaaabb;经过filter过滤后,s仍然为6,但内容变为了aaaaccc,长度变成了7,根据反序列化读取变量的原则来讲,此时的name能读取到的只是aaaacc,末尾处的那个c是读取不到的,这就形成了一个字符串的逃逸。当我们添加多个bb,每添加一个bb我们就能逃逸一个字符,那我们将逃逸的字符串的长度填充成我们要反序列化的代码长度的话那就可以控制反序列化的结果以及类里面的变量值了。
假如我们要在name处改为上一个";s:4:"pass";s:6:"hacker";}
来间接修改pass的值,如果我们只是单纯的把它加进去的话,就像下面这样:
class A{
public $name='";s:4:"pass";s:6:"hacker";}';
public $pass='123456';
}
由于$name
被序列化后的长度是固定的,在反序列化后$name
仍然为";s:4:"pass";s:6:"hacker";}(长度为27)
,$pass
仍然为123456
:
这里的关键点在于filter函数,这个函数检测并替换了非法字符串,看似增加了代码的安全系数,实则让整段代码更加危险。filter函数中检测序列化后的字符串,如果检测到了非法字符'bb',就把它替换为'ccc'。
此时我们发现";s:4:"pass";s:6:"hacker";}的长度为27,如果我们再加上54个bb,那最终的长度将增加27(也就是81个c),不就能逃逸后面的";s:4:"pass";s:6:"hacker";}了吗,这样真实的pass为123456就被丢弃了,识别的以为pass是hacker。
可见,成功逃逸,成功修改了pass的值。
具体分析如下:
逃逸或者说被 “顶” 出来的payload就会被当做当前类的属性被继续执行。
这里插入一点我自己的理解
(这里我在大佬源代码的基础上又改了一点,改成1个b换成两个c(感觉好像能看出点什么规律)
- 1个b换成两个c
这里需要注意的是,我们需要挤出去的payload为";s:4:"pass";s:6:"hacker";}(27长度)
- 那我们再试试改成1个b可以换4个c
这里我是列方程式做出来的
我们不妨列方程式(?代表输入b的倍数)
?b+27=4?b,解得b=9,所以我们这里输9个b就能溢出
- 这里我又做了变形,把bb能换成ccccc
照样是列方程组,2b=5c,那方程组就是
27+?b=5/2?b,解得?b=18,b输入18就得到了
(这里仅因为这几道题得到的结论,如果有不对的地方欢迎各位大佬来指正,纯利用数学关系的思维)
附上代码:
<?php
function filter($str){return str_replace('bb', 'ccccc', $str);
}
class A{public $name='bbbbbbbbbbbbbbbbbb";s:4:"pass";s:6:"hacker";}';public $pass='123456';
}
$AA=new A();
//echo serialize($AA)."\n";
$res=filter(serialize($AA));
echo $res."\n";
$c=unserialize($res);
print_r($c)."\n";
echo $c->pass;
替换之后导致序列化字符串变短
(这里依旧是借用大佬的代码)
注:
我这里本来用idea去实操,还是换成了cmd比较方便,cmd中使用php命令的教程如下
win命令行执行php_windows使用php8.2得在终端输入命令后才能使用-CSDN博客
参考文章:
Java基础——对象的序列化(通俗易懂,排版优美)_通过序列化机制来创建对象什么意思是-CSDN博客
php反序列化小记(1) | Prove yourself