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

Apache Tomcat的默认servlet在处理文件上传时的TOCTOU条件竞争问题。当默认servlet被配置为允许写入(即readonly初始化参数被设置为非默认值false),在不区分大小写的文件系统上,攻击者可以利用并发读取和上传同一个文件的机会,绕过Tomcat的大小写敏感性检查,导致上传的文件被错误地当作JSP文件处理,从而可能触发远程代码执行。

一、漏洞简介

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

二、影响版本

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

三、漏洞原理分析

前置知识

条件竞争::条件竞争是指在多线程或多进程环境中,两个或多个执行线程(或进程)试图同时访问共享数据,并且至少有一个线程(或进程)试图修改数据,导致程序的输出依赖于线程(或进程)执行的顺序,这种顺序是不可预测的。条件竞争可能导致程序行为不稳定,结果不一致,甚至产生错误。
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文件执行,最终造成远程命令执行。

四、环境搭建

基础环境

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文件:

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

五、漏洞复现

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

image.png

image.png

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

image.png

六、总结

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

  • 发表于 2025-01-13 10:03:43
  • 阅读 ( 6964 )
  • 分类:Web服务器

0 条评论

请先 登录 后评论
菜鸡小z
菜鸡小z

1 篇文章

站长统计