浅谈PHP反序列化字符逃逸

本文详细介绍了PHP序列化和反序列化的过程,特别是字符逃逸现象,包括字符变多和字符减少两种情况,通过实例解析如何利用字符长度差异导致的反序列化错误进行逃逸,从而影响程序执行。
摘要由CSDN通过智能技术生成

前言

最近遇到了这个知识点,网上找了好几篇这方面的好像都不太仔细,自己来总结下

0x01 序列化和反序列化

serialize()

所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。
序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字
如下面的:

<?php
class people{
	public $name = 'lonmar';
	protected $age = 99;
	private $sex = 'man';
}
$lonmar = new people();
var_dump(serialize($lonmar));
// string(88) "O:6:"people":3:{s:4:"name";s:6:"lonmar";s:6:"<0x00>*<0x00>age";i:99;s:11:"<0x00>people<0x00>sex";s:3:"man";}"	

在这里插入图片描述
对于对象,序列化后的格式为:
O:strlen(类名):类名:类的变量个数:{类型:长度:值;类型:长度:值…}

其他类型的数据序列化后的格式为:

String类型 :s:size:value;
Integer类型 :i:value;
Boolean类型 : b:value;(保存1或0)
Null型 :N;
Array :a:size:{key definition;value definition}

对象中成员的形式,不同属性的差别主要体现在key上,而且key一定是string,直接给出模式,具体形式可以参考上面的示例:

public: s:size:value size是string长度
protected: s:size+3:<0x00>*<0x00>value 这里<0x00>是一个字符,十六进制为00
private: s:size+strlen(class_name)+2:<0x00>class_name<0x00>value

还有需要注意的点是;分割不同字段}结尾,这对反序列化很重要

unserialize()

unserialize() 对单一的已序列化的变量进行操作,将其转换回 PHP 的值

反序列化特点

  1. php在反序列化时,底层代码是以 ;作为字段的分隔,以 } 作为结尾

这会造成,随便在序列化数据后添加一些无用字符,反序列化的时候也会被忽略,因为遇到了;}会忽略后面的字符
在这里插入图片描述

  1. unserialize根据长度判断内容,长度不对应的时候会报错
    在这里插入图片描述
    或者下面这个例子:
    在这里插入图片描述

  2. 可以反序列化类中不存在的元素

在这里插入图片描述

0x02 字符逃逸

字符逃逸某种角度上和sql注入类似,都是通过构造闭合的方式构造payload,只不过字符逃逸构造闭合注意点在长度,因为闭合标志固定,都是;}

前面提到在unserialize的时候, 当你的字符串长度与所描述的长度不一样时就会报错.比如 s:3:"Tom"变成s:4:"Tom"或s:2:"Tom"就会报错. 可以通过拼接字符串的方式来使它不报错

所以字符逃逸又分为两类

  1. 字符变多
  2. 字符变少

字符变多

例子:

<?php
highlight_file(__file__);
function filter($str){
    return str_replace('l', 'll', $str);
}

class person{
	public $name = 'lonmar';
	public $age = '100';
}
$test = new person();
$test = serialize($test);
echo "</br>";
print_r($test);
echo "</br>";
$test = filter($test);
print_r($test);
print_r(unserialize($test));

在这里插入图片描述

因为替换过后,实际长度为7,而描述长度为6,少读了一个r 所以失败

这种字符增多是反序列化失败是因为漏读了字符串的value,如果构造恶意的value,再故意漏读

如令$name='lonmar";s:3:"age";s:2:"35";}'

如果再进行替换,lonmar=>llonmar,后面的}又读不到

再多几个l,lllllllllllllllllllllonmar=>llllllllllllllllllllllllllllllllllllllllllonmar ,";s:3:"age";s:2:"35";}就又读不到

只能读到lllllllllllllllllllllonmar这样后面的;s:3:“age”;s:2:“35”;}就逃逸掉了,逃逸掉的字符串可以把原来后面的正常序列化数据提前闭合掉.(闭合条件";}

;s:3:"age";s:2:"35";}长度是22 , 所以只需要22个l
如下:

<?php
function filter($str){
    return str_replace('l', 'll', $str);
}

class person{
	public $name = 'llllllllllllllllllllllonmar";s:3:"age";s:2:"35";}';
	public $age = '100';
}
$test = new person();
$test = serialize($test);
var_dump($test);
$test = filter($test);
var_dump($test);
var_dump(unserialize($test));

这里类名做了替换people=>person 因为people里也有l

在这里插入图片描述
可以观察到age变成了35,
name不是llllllllllllllllllllllllllllllllllllllllllllonmar";s:3:"age";s:2:"35";} 而是 llllllllllllllllllllllllllllllllllllllllllllonmar
因为;s:3:“age”;s:2:“35”;}逃逸,之后终止标志变成了;s:3:“age”;s:2:“35”;}里的;} 后面的就被忽略了

字符减少

字符减少,反序列化的时候就会多读

<?php
highlight_file(__file__);
function filter($str){
    return str_replace('ll', 'l', $str);
}

class person{
	public $name = 'lonmar';
	public $age = '100';
}

同样的,如果构造恶意的age,让反序列化的时候多读,把age一部分读进去 同样可以达到某种目的

正常的数据 O:6:"person":2:{s:4:"name";s:6:"lonmar";s:3:"age";s:3:"xxx";}

如果做替换,让也";s:3:"age";s:3:"被读进name,再把xxx替换为;s:3:“age”;s:3:“100”;}

$age=123";s:3:"age";s:3:"100";}

O:6:"person":2:{s:4:"name";s:47:"llllllllllllllllllllllllllllllllllllllllllonmar";s:3:"age";s:26:"123";s:3:"age";s:3:"111";}";}

多读的为 ";s:3:"age";s:26:"123 长度 21

构造(l*42)nmar, 就多吞了部分字符串,

name:
llllllllllllllllllllllllllllllllllllllllllonmar =>
lllllllllllllllllllllonmar";s:3:"age";s:26:"123

age:

123";s:3:"age";s:3:"111";}
=>
111
在这里插入图片描述

总结

大致就像前面例子那样,根据字符增多或者减少,让该成员反序列化时,部分字符逃逸或者吞掉后面成员的部分字符达到一定目的

具体题目: https://blog.csdn.net/weixin_45551083/article/details/111084908

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值