最近Tomcat新爆出的条件竞争RCE漏洞挺火,本人特意研究了一下其原理,在此分享一下。
官方的漏洞公告在这里:
[SECURITY] CVE-2024-50379 Apache Tomcat - RCE via write-enabled default servlet
翻译:
如果默认 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
缓解措施:
升级至 Apache Tomcat 11.0.2 或更高版本
升级至 Apache Tomcat 10.1.34 或更高版本
升级至 Apache Tomcat 9.0.98 或更高版本
虽然官方说升级到9.0.98可以缓解,但本人就是用的9.0.98版本依然复现了,不知道是不是官方写错了。
漏洞复现
1. 下载 tomcat-9.0.98 windows版:apache-tomcat-9.0.98-windows-x64.zip
2. 配置web.xml
<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>
<!-- 将readonly设置为false -->
<init-param>
<param-name>readonly</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
3. 启动tomcat,在apache-tomcat-9.0.98\bin目录下双击startup.bat
4. 执行POC脚本:tomcat_CVE-2024-50379.py
不出意外的话,几秒钟后会弹出计算器:
漏洞原理
要了解其原理,需要先写一段Java代码:
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);
}
}
}
当你的磁盘目录上不存在poc2.jsp时,会看到这样的输出:
执行PUT /poc2.JSP 上传一个poc2.JSP文件:
然后,又看到下面的输出:
再等待几秒后会发现:小写的jsp后缀变为了大写的JSP:
这是JDK 1.8 java.io.File#getCanonicalPath 方法在Windows上的表现。Tomcat的这个漏洞也正是利用了这个特点。
在tomcat源码中,org.apache.catalina.webresources.AbstractFileResourceSet#file 方法第94行调用了getCanonicalPath方法,
当刚刚执行 PUT /poc2.JSP 操作后,canPath是一个小写的 .jsp 结尾的文件,
然后回到上层getResource方法中,第116行判断文件是否存在,由于此时已经上传了 poc*.JSP 文件,f.exists()会返回true(windows大小写不敏感),于是在第122行返回了一个 FileResource 对象,有了这个FileResource对象tomcat就会找到相应的jsp并编译成servlet执行,于是RCE就触发了。
为什么再过几秒之后就无法触发了呢?因为过了这个时间差之后,file.getCanonicalPath 获取的值就变为了 *.JSP 大写结尾的路径,但调用 file.getAbsolutePath() 方法依然会返回 *.jsp 小写结尾的路径,于是 file 方法第138行的equals方法会返回false,再取反为true,于是执行到145行返回一个null:
再返回上层getResource方法中,此时得到的 f 是null,则会返回一个空资源 EmptyResource:
空资源代表不存在,于是tomcat会返回404 。所以这个漏洞要想利用必须打时间差,在 .jsp 变为 .JSP 之前。