概要 https://www.veracode.com/blog/research/exploiting-jndi-injections-java 跟着这文章调了一遍, 之前一度以为在jdk 8u191之后, JNDI注入也就只能打打反序列了,看了这文章后发现了一种新的场景。 之前JNDI注入都是依靠于getObjectFactoryFromReference时, 如果目标classpath里找不到指定的class时,会从远程codebase中下载class字节码, 然后实例化。 在出现了trustCodebaseURL的限制之后 已经不再能够从codebase中下载字节码。 但是可以loadClass目标classpath下存在的类。
Tomcat 8 依赖包pom.xml
1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > org.apache.tomcat</groupId > <artifactId > tomcat-catalina</artifactId > <version > 8.5.0</version > </dependency > <dependency > <groupId > org.apache.el</groupId > <artifactId > com.springsource.org.apache.el</artifactId > <version > 7.0.26</version > </dependency >
JNDIClient.java
1 2 3 4 5 6 7 8 9 10 11 import javax.naming.Context;import javax.naming.InitialContext;public class JNDIClient { public static void main (String[] args) throws Exception { String uri = "rmi://localhost:1097/Object" ; Context ctx = new InitialContext(); ctx.lookup(uri); } }
调用的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public Object lookup (Name var1) throws NamingException { if (var1.isEmpty()) { return new RegistryContext(this ); } else { Remote var2; try { var2 = this .registry.lookup(var1.get(0 )); } catch (NotBoundException var4) { throw new NameNotFoundException(var1.get(0 )); } catch (RemoteException var5) { throw (NamingException)wrapRemoteException(var5).fillInStackTrace(); } return this .decodeObject(var2, var1.getPrefix(1 )); } }
该方法对RMI registry发请求,反序列获取到ReferenceWrapper_Stub 然后把反序列得到的ReferenceWrapper_Stub传给decodeObject
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private Object decodeObject (Remote var1, Name var2) throws NamingException { try { Object var3 = var1 instanceof RemoteReference ? ((RemoteReference)var1).getReference() : var1; Reference var8 = null ; if (var3 instanceof Reference) { var8 = (Reference)var3; } else if (var3 instanceof Referenceable) { var8 = ((Referenceable)((Referenceable)var3)).getReference(); } if (var8 != null && var8.getFactoryClassLocation() != null && !trustURLCodebase) { throw new ConfigurationException("The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'." ); } else { return NamingManager.getObjectInstance(var3, var2, this , this .environment); }
在decodeObject中, 给获取到的ReferenceWrapper_Stub调用getReference方法, getReference方法通过获取ReferenceWrapper_Stub的ref属性然后发请求, 反序列请求结果得到真正绑定到RMI Registry上的对象(ResourceRef), 然后传给NamingManager.getObjectInstance方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public static Object getObjectInstance(Object refInfo, Name name, Context nameCtx, Hashtable<?,?> environment) throws Exception { ....................... Reference ref = null ; if (refInfo instanceof Reference) { ref = (Reference) refInfo; } else if (refInfo instanceof Referenceable) { ref = ((Referenceable)(refInfo)).getReference(); } if (ref != null ) { String f = ref.getFactoryClassName(); if (f != null ) { factory = getObjectFactoryFromReference(ref, f); if (factory != null ) { return factory.getObjectInstance(ref, name, nameCtx, environment); }
首先类型转换将object转换为Reference对象, 然后ref.getFactoryClassName() 获取FactoryClassName
1 2 3 4 5 6 7 8 9 public final String getFactoryClassName () { String factory = super .getFactoryClassName(); if (factory != null ) { return factory; } else { factory = System.getProperty("java.naming.factory.object" ); return factory != null ? null : this .getDefaultFactoryClassName(); } }
1 2 3 public String getFactoryClassName () { return classFactory; }
返回的是Reference对象的classFactory属性。 获取到之后又传递给了getObjectFactoryFromReference方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static ObjectFactory getObjectFactoryFromReference ( Reference ref, String factoryName) throws IllegalAccessException, InstantiationException, MalformedURLException { Class<?> clas = null ; try { clas = helper.loadClass(factoryName); } catch (ClassNotFoundException e) { } String codebase; if (clas == null && (codebase = ref.getFactoryClassLocation()) != null ) { try { clas = helper.loadClass(factoryName, codebase); } catch (ClassNotFoundException e) { } } return (clas != null ) ? (ObjectFactory) clas.newInstance() : null ; }
然后loadClass, 再newInstance实例化该类。 因为newInstance必然只会调用无参构造方法,所以该class需要有定义一个无参的构造方法或者是根本无构造方法(在无任何构造方法的情况下会隐式生成一个无参构造方法), 如果没有无参构造方法newInstance就直接出错了。
1 2 3 4 5 factory = getObjectFactoryFromReference(ref, f); if (factory != null ) { return factory.getObjectInstance(ref, name, nameCtx, environment); }
在实例化该类后 还会调用这对象的getObjectInstance方法, 所以如果能在一些常用的库中找到有getObjectInstance方法 并且在该方法中有做一些危险的事情的话, 那么就有用了。
原文大佬找到了org.apache.naming.factory.BeanFactory类,实现了ObjectFactory接口。 那么必然实现了ObjectFactory接口的getObjectInstance方法,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public Object getObjectInstance (Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws NamingException { if (obj instanceof ResourceRef) { try { 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 var26) { ; } } else { try { beanClass = Class.forName(beanClassName); } catch (ClassNotFoundException var25) { var25.printStackTrace(); } } if (beanClass == null ) { throw new NamingException("Class not found: " + beanClassName); } else { BeanInfo bi = Introspector.getBeanInfo(beanClass); PropertyDescriptor[] pda = bi.getPropertyDescriptors(); Object bean = beanClass.getConstructor().newInstance(); RefAddr ra = ref.get("forceString" ); Map<String, Method> forced = new HashMap(); String value; String propName; int i; if (ra != null ) { value = (String)ra.getContent(); Class<?>[] paramTypes = new Class[]{String.class}; String[] var18 = value.split("," ); i = var18.length; for (int var20 = 0 ; var20 < i; ++var20) { String param = var18[var20]; param = param.trim(); int index = param.indexOf(61 ); if (index >= 0 ) { propName = param.substring(index + 1 ).trim(); param = param.substring(0 , index).trim(); } else { propName = "set" + param.substring(0 , 1 ).toUpperCase(Locale.ENGLISH) + param.substring(1 ); } try { forced.put(param, beanClass.getMethod(propName, paramTypes)); } catch (SecurityException | NoSuchMethodException var24) { throw new NamingException("Forced String setter " + propName + " not found for property " + param); } }
在该方法中 可以明显的看到反射过程。 并且反射的类等东西都来自Reference对象。 反射的类来自ref.getClassName() 反射调用的方法 来自ref.get(“forceString”),如果forceString属性值中含有=号, 那么=号右边的值就为获取的方法, 左边值为hashmap的key, 如果属性值中没有等号就会获取该属性值的setter方法。
最后获取到一个StringRefAddr对象, 且该对象的addrtype属性值非factory,scope,auth,forceString,singleton时, 获取该对象的addrtype作为hashmap的key 从hashmap中取出之前存入的方法, 并且将该对象的contents属性作为反射调用方法时的值。
1 2 Class<?>[] paramTypes = new Class[]{String.class}; beanClass.getMethod(propName, paramTypes)
并且获取方法的时候,指定了该方法只能有一个String参数。
原文大佬在这里反射的是javax.el.ELProcessor类, 调用eval方法进行el注入 实现RCE.
1 2 3 public Object eval (String expression) { return this .getValue(expression, Object.class); }
TOMCAT 7 TOMCAT 7测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <dependencies > <dependency > <groupId > org.apache.tomcat</groupId > <artifactId > tomcat-catalina</artifactId > <version > 7.0.91</version > </dependency > <dependency > <groupId > org.apache.el</groupId > <artifactId > com.springsource.org.apache.el</artifactId > <version > 7.0.26</version > </dependency > </dependencies >
出异常, 没有javax.el.ELProcessor这个类。
在TOMCAT>8.5版本中, 存在el包
在tomcat7中没有这个el包。
在tomcat8中, 依赖了tomcat-jsp-api包 jsp-api包又依赖了el包。
在tomcat7中, 并没有依赖tomcat-jsp-api, 就没有了el包。 所以在tomcat7中 还需要再手动引入这个包。
tomcat el包和 javax.el包同时存在时 tomcat的el包名和javax.el的包名相同, 都为javax.el 存在两个javax.el.ELProcessor 在import这个类的时候, 具体引入的哪个类跟编译器先载入哪个jar包有关。 maven中, 哪个dependency在前就会导入哪个类。 pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <dependencies > <dependency > <groupId > javax.el</groupId > <artifactId > javax.el-api</artifactId > <version > 3.0.1-b06</version > </dependency > <dependency > <groupId > com.sun.el</groupId > <artifactId > el-ri</artifactId > <version > 1.0</version > </dependency > <dependency > <groupId > org.apache.tomcat</groupId > <artifactId > tomcat-catalina</artifactId > <version > 8.5.34</version > </dependency > <dependency > <groupId > org.apache.el</groupId > <artifactId > com.springsource.org.apache.el</artifactId > <version > 7.0.26</version > </dependency > </dependencies >
javax.el包下的ELProcessor没法像tomcat el包下的ELProcessor一样EL注入调用方法, 直接就出错了。
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <dependencies > <dependency > <groupId > org.apache.tomcat</groupId > <artifactId > tomcat-catalina</artifactId > <version > 8.5.34</version > </dependency > <dependency > <groupId > org.apache.el</groupId > <artifactId > com.springsource.org.apache.el</artifactId > <version > 7.0.26</version > </dependency > <dependency > <groupId > javax.el</groupId > <artifactId > javax.el-api</artifactId > <version > 3.0.1-b06</version > </dependency > <dependency > <groupId > com.sun.el</groupId > <artifactId > el-ri</artifactId > <version > 1.0</version > </dependency > </dependencies >
References https://www.veracode.com/blog/research/exploiting-jndi-injections-java