java内存马连续剧——Valve内存马
0x01 什么是Valve
我们知道,tomcat是由连接器(Connector)和容器(Container)两部分组成。Connector主要作用是监听并且转换为socket请求,然后再交给Container处理,Container里有四类子容器,分别为Engine,Host,Context,Wrapper,这四种容器层层嵌套,我们需要将消息传递到最底层了Wrapper,也就是Servlet里,该怎么传递?
tomcat的管道机制
也称pipeline机制,pipeline就是管道,上面所说的四个容器都有自己的pipeline组件,每个Pipeline组件上至少会设定一个Valve(也就是阀门)我们可以把http请求比作在pipeline(管道)流淌的水,那么阀门(Valve)就可以用来在管道中实现各种功能,如控制流速等。通过管道机制,我们能按照需求,给在不同子容器中流通的请求添加各种不同的业务逻辑,并提前在不同子容器中完成相应的逻辑操作。
Pipeline 中会有一个最基础的 Valve ,为basic Valve,在最末端执行
我们可以自己尝试写进去一个Valve
0x02 Valve内存马流程分析
Valve处理业务逻辑是通过调用重写的invoke方法来实现的,所以自定义一个Valve要继承ValveBase,在invoke方法里添加一些命令执行的代码,一个恶意的Valve就写好了。
package Valve; |
了解管道机制后,我们知道pipeline里有多个Valve,最后一个为basicValve,有点像之前分析的Filter内存马里的FilterChain,新的Valve会顺序添加到basicValve的前面。关键是自己写的恶意的Valve怎么添加到这个链里。
将Valve添加到链中
pipeline提供了addValve方法,可以将新的Valve添加到链中,addValve 方法对应的实现类是 StandardPipeline,我们无法直接获取到,可以通过StandardContext简介获取
全局搜索Pipeline,有这样一个方法
我们跟进getPipeline方法
其实获取的就是StandardPipeline,所以思路就清晰了,我们可以通过反射来获取 StandardContext 上下文,然后利用StandardContext.getPipeline().addValve() 添加恶意的Valve
Valve的invoke调用
我们把断点下在 CoyoteAdapter#service方法中
这前面是对Request和Respone对象进行一些判断及创建的操作,不用管,直接往后调
connector就是所说的连接器,调用 getService 方法就是获取与它关联的服务
然后调用 getContainer().getPipeline()获取容器里的pipeline管道
也就是 StandardPipeline,调用getFirst 方法获取第一个Valve,跟进
没有添加任何Valve,就获取基础的Valve,调用它的invoke方法实现业务逻辑
然后继续调用后续的invoke方法。
0x03 编写exp
写一个恶意的Valve
通过反射获取StandardContext
通过StandardContext 获取StandardPipeline
调用StandardPipeline的addValve方法将Valve添加进去
今儿又知道一个获取 standardContext 的方法,这个代码更短
Field req = request.getClass().getDeclaredField("request"); |
通过 ServletRequest 获取与当前请求相关联的 Servlet 上下文
然后就是获取StandardPipeline,调用addValve方法了
Pipeline pipeline = standardContext.getPipeline(); |
完整的exp:
<%@ page import="org.apache.catalina.valves.ValveBase" %> |
接下来任何路由都可以执行命令。重启服务器后即可清除内存马。