Apache Tomcat 条件竞争致远程代码执行漏洞(CVE-2024-50379)

Apache Tomcat 条件竞争致远程代码执行漏洞(CVE-2024-50379)

菜鸡小z 无影安全实验室 2025-02-17 14:35

免责声明:
本篇文章仅用于技术交流,
请勿利用文章内的相关技术从事非法测试

由于传播、利用本公众号无影安全
实验室所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号无影安全实验室及作者不为此承担任何责任,一旦造成后果请自行承担!
如有侵权烦请告知,我们会立即删除并致歉。谢谢!

朋友们现在只对常读和星标的公众号才展示大图推送,建议大家把”无影安全实验室
“设为星标,这样更新文章也能第一时间推送!

安全工具

0x01 漏洞简介

关于Apache Tomcat中TOCTOU(Time-of-check Time-of-use)条件竞争漏洞,主要发生在JSP编译期间。具体来说,这个漏洞涉及到Apache Tomcat的默认servlet在处理文件上传时的TOCTOU条件竞争问题。当默认servlet被配置为允许写入(即readonly初始化参数被设置为非默认值false),在不区分大小写的文件系统上,攻击者可以利用并发读取和上传同一个文件的机会,绕过Tomcat的大小写敏感性检查,导致上传的文件被错误地当作JSP文件处理,从而可能触发远程代码执行。

0x02 影响版本

Apache Tomcat 11.0.0-M1 – 11.0.1

Apache Tomcat 10.1.0-M1 – 10.1.33

Apache Tomcat 9.0.0.M1 – 9.0.97

0x03 漏洞原理分析

前置知识

条件竞争:
:条件竞争是指在多线程或多进程环境中,两个或多个执行线程(或进程)试图同时访问共享数据,并且至少有一个线程(或进程)试图修改数据,导致程序的输出依赖于线程(或进程)执行的顺序,这种顺序是不可预测的。条件竞争可能导致程序行为不稳定,结果不一致,甚至产生错误。
TOCTOU(检查时间与使用时间)
:是条件竞争的一个特定类型,发生在一个程序或系统在检查了某个条件(例如文件存在性、权限等)之后,但在实际使用该条件所涉及的资源之前,该资源的状态被另一个线程、进程或用户改变的情况下。这种漏洞可能导致安全检查失败,因为实际使用资源时的状态与之前检查时的状态不一致。

原理分析:

image.png

如果readonly=false时,可以执行PUT和DELETE方法。此时去查看PUT方法实现(apache-tomcat-9.0.63-src\java\org\apache\catalina\servlets\DefaultServlet.java,这个文件是Apache Tomcat服务器中用于处理静态资源请求的默认Servlet。)

protected void doPut(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
        if (readOnly) {
            sendNotAllowed(req, resp);
            return;
        }
        String path = getRelativePath(req);
        WebResource resource = resources.getResource(path);
        Range range = parseContentRange(req, resp);
        if (range == null) {
            // Processing error. parseContentRange() set the error code
            return;
        }
        InputStream resourceInputStream = null;
        try {
            // Append data specified in ranges to existing content for this
            // resource - create a temp. file on the local filesystem to
            // perform this operation
            // Assume just one range is specified for now
            if (range == IGNORE) {
                resourceInputStream = req.getInputStream();
            } else {
                File contentFile = executePartialPut(req, range, path);
                resourceInputStream = new FileInputStream(contentFile);
            }
            if (resources.write(path, resourceInputStream, true)) {
                if (resource.exists()) {
                    resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
                } else {
                    resp.setStatus(HttpServletResponse.SC_CREATED);
                }
            } else {
                resp.sendError(HttpServletResponse.SC_CONFLICT);
            }
        } finally {
            if (resourceInputStream != null) {
                try {
                    resourceInputStream.close();
                } catch (IOException ioe) {
                    // Ignore
                }
            }
        }
    }

上述代码处理HTTP PUT请求,允许客户端上传文件到服务器的指定路径。它处理了范围请求、部分更新,并确保了资源的正确写入。如果在处理过程中发生错误,它会发送适当的HTTP状态码响应给客户端。

public WebResource getResource(String path) {
        checkPath(path);
        String webAppMount = getWebAppMount();
        WebResourceRoot root = getRoot();
        if (path.startsWith(webAppMount)) {
            File f = file(path.substring(webAppMount.length()), false);
            if (f == null) {
                return new EmptyResource(root, path);
            }
            if (!f.exists()) {
                return new EmptyResource(root, path, f);
            }
            if (f.isDirectory() && path.charAt(path.length() - 1) != '/') {
                path = path + '/';
            }
            return new FileResource(root, path, f, isReadOnly(), getManifest());
        } else {
            return new EmptyResource(root, path);
        }
    }

上述代码的目的是提供一个安全的方式来访问Web应用的资源。它首先验证路径,然后根据路径获取资源,如果资源不存在或路径不合法,则返回一个空资源对象。这样可以确保对资源的访问是受控的,并且能够处理不同的情况,如文件不存在或路径错误。(apache-tomcat-9.0.63-src\java\org\apache\catalina\webresources\DirResourceSet.java,实现了WebResourceSet
接口,DirResourceSet
代表一个基于目录的资源集合,通常用于提供对Web应用程序目录中资源的访问。)

 File resource, boolean readOnly, Manifest manifest) {
        super(root,webAppPath);
        this.resource = resource;
        if (webAppPath.charAt(webAppPath.length() - 1) == '/') {
            String realName = resource.getName() + '/';
            if (webAppPath.endsWith(realName)) {
                name = resource.getName();
            } else {
                // This is the root directory of a mounted ResourceSet
                // Need to return the mounted name, not the real name
                int endOfName = webAppPath.length() - 1;
                name = webAppPath.substring(
                        webAppPath.lastIndexOf('/', endOfName - 1) + 1,
                        endOfName);
            }
        } else {
            // Must be a file
            name = resource.getName();
        }
        this.readOnly = readOnly;
        this.manifest = manifest;
        this.needConvert = PROPERTIES_NEED_CONVERT && name.endsWith(".properties");
    }

这个构造函数初始化了一个FileResource
对象,设置了资源的路径、实际文件、名称、只读标志和是否需要转换属性文件等属性。这使得FileResource
能够正确地管理和提供对文件系统资源的访问。(apache-tomcat-9.0.63-src\java\org\apache\catalina\webresources\FileResource.java,这个类实现了 WebResource
 接口,代表文件系统中的一个具体文件或目录,作为 Web 应用程序的一部分资源提供给客户端。)

结合上述代码可以看到如果readonly为false时,可以并发执行PUT和GET方法,但在并发执行时没有文件锁机制,无法保证资源访问的原子性和一致性,又因为处理请求存在时间窗口,如果频繁发送请求可能会导致f.exists跳过资源检查,如果在windows这种大小写不敏感的文件系统中,则会绕过Tomcat的大小写检查,将Jsp文件认为是jsp文件执行,最终造成远程命令执行。

0x04 环境搭建

基础环境

Jdk8u151:
https://repo.huaweicloud.com/java/jdk/8u151-b12/

Tomcat-9.0.63:
https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.63/bin/

BurpSuite

Windows 10

JDK需配置环境变量,tomcat下载解压之后需要修改apache-tomcat-9.0.63-src\conf\web.xml文件:


然后进入bin目录执行startup.bat,在浏览器访问127.0.0.1:8080即可:

****## 0x04 漏洞复现

①使用BurpSuite访问127.0.0.1:8080时抓包,发送至intruder模块两个包,分别是上传JSP远程代码执行文件和访问读取JSP文件

image.png

image.png

②均使用sniper攻击模式同时开始并发攻击,等待弹出计算机,漏洞复现完成。

image.png

六、总结

漏洞原理分析如有错误,欢迎指正。