概要
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);
}
|
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>
|
出异常, 没有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>
<!-- 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
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>
|
References
https://www.veracode.com/blog/research/exploiting-jndi-injections-java