https://www.veracode.com/blog/research/exploiting-jndi-injections-java
跟着这文章调了一遍, 之前一度以为在jdk 8u191之后, JNDI注入也就只能打打反序列了,看了这文章后发现了一种新的场景。
之前JNDI注入都是依靠于getObjectFactoryFromReference时,
如果目标classpath里找不到指定的class时,会从远程codebase中下载class字节码, 然后实例化。
在出现了trustCodebaseURL的限制之后 已经不再能够从codebase中下载字节码。 但是可以loadClass目标classpath下存在的类。
依赖包pom.xml
<dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-catalina</artifactId> <version>8.5.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.el/com.springsource.org.apache.el --> <dependency> <groupId>org.apache.el</groupId> <artifactId>com.springsource.org.apache.el</artifactId> <version>7.0.26</version> </dependency>
JNDIClient.java
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); } }
调用的方法
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
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方法。
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) { // if reference identifies a factory, use exclusively factory = getObjectFactoryFromReference(ref, f); if (factory != null) { return factory.getObjectInstance(ref, name, nameCtx, environment); }
首先类型转换将object转换为Reference对象, 然后ref.getFactoryClassName() 获取FactoryClassName
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(); } }
public String getFactoryClassName(){ return classFactory; }
返回的是Reference对象的classFactory属性。
获取到之后又传递给了getObjectFactoryFromReference方法
static ObjectFactory getObjectFactoryFromReference( Reference ref, String factoryName) throws IllegalAccessException, InstantiationException, MalformedURLException { Class<?> clas = null; // Try to use current class loader try { clas = helper.loadClass(factoryName); } catch (ClassNotFoundException e) { // ignore and continue // e.printStackTrace(); } // All other exceptions are passed up. // Not in class path; try to use codebase 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就直接出错了。
factory = getObjectFactoryFromReference(ref, f); if (factory != null) { return factory.getObjectInstance(ref, name, nameCtx, environment); }
在实例化该类后 还会调用这对象的getObjectInstance方法,
所以如果能在一些常用的库中找到有getObjectInstance方法 并且在该方法中有做一些危险的事情的话, 那么就有用了。
原文大佬找到了org.apache.naming.factory.BeanFactory类,实现了ObjectFactory接口。
那么必然实现了ObjectFactory接口的getObjectInstance方法,
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属性作为反射调用方法时的值。
Class<?>[] paramTypes = new Class[]{String.class}; beanClass.getMethod(propName, paramTypes)
并且获取方法的时候,指定了该方法只能有一个String参数。
原文大佬在这里反射的是javax.el.ELProcessor类, 调用eval方法进行el注入 实现RCE.
public Object eval(String expression){ return this.getValue(expression, Object.class); }
TOMCAT 7测试。
<dependencies> <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-catalina --> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-catalina</artifactId> <version>7.0.91</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.el/com.springsource.org.apache.el --> <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的包名相同, 都为javax.el
存在两个javax.el.ELProcessor
在import这个类的时候, 具体引入的哪个类跟编译器先载入哪个jar包有关。
maven中, 哪个dependency在前就会导入哪个类。
pom.xml
<dependencies> <!-- https://mvnrepository.com/artifact/javax.el/javax.el-api --> <dependency> <groupId>javax.el</groupId> <artifactId>javax.el-api</artifactId> <version>3.0.1-b06</version> </dependency> <!-- https://mvnrepository.com/artifact/com.sun.el/el-ri --> <dependency> <groupId>com.sun.el</groupId> <artifactId>el-ri</artifactId> <version>1.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-catalina --> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-catalina</artifactId> <version>8.5.34</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.el/com.springsource.org.apache.el --> <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
<dependencies> <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-catalina --> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-catalina</artifactId> <version>8.5.34</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.el/com.springsource.org.apache.el --> <dependency> <groupId>org.apache.el</groupId> <artifactId>com.springsource.org.apache.el</artifactId> <version>7.0.26</version> </dependency> <!-- https://mvnrepository.com/artifact/javax.el/javax.el-api --> <dependency> <groupId>javax.el</groupId> <artifactId>javax.el-api</artifactId> <version>3.0.1-b06</version> </dependency> <!-- https://mvnrepository.com/artifact/com.sun.el/el-ri --> <dependency> <groupId>com.sun.el</groupId> <artifactId>el-ri</artifactId> <version>1.0</version> </dependency> </dependencies>