Exploitng JNDI Injection In Java

概要

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>
<!-- 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

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) {
// 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

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 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就直接出错了。

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);
}

-w1328

TOMCAT 7

TOMCAT 7测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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>

-w970
出异常, 没有javax.el.ELProcessor这个类。

在TOMCAT>8.5版本中, 存在el包
-w338

在tomcat7中没有这个el包。
-w388

在tomcat8中, 依赖了tomcat-jsp-api包
-w884
-w360
jsp-api包又依赖了el包。

在tomcat7中,
-w690
并没有依赖tomcat-jsp-api, 就没有了el包。
所以在tomcat7中 还需要再手动引入这个包。
-w920

tomcat el包和 javax.el包同时存在时

tomcat的el包名和javax.el的包名相同, 都为javax.el
-w761
存在两个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>
<!-- 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>

-w1119
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>
<!-- 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>

-w1056

References

https://www.veracode.com/blog/research/exploiting-jndi-injections-java