This article explains
This article is translated and compiled from IoTSec.in original article, with the original author being the author of this article. The Chinese version is mainly aimed at readers in the Chinese security community, with technical content consistent with the original.
Original: a9cK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6A6L8%4c8K6k6h3y4Q4x3X3g2A6L8W2)9J5c8Y4c8Q4x3V1k6A6i4K6u0V1k6X3!0#2L8X3c8Q4x3X3c8S2i4K6u0V1M7X3g2S2L8q4)9J5k6r3y4$3k6g2)9J5k6r3W2F1i4K6u0V1j5%4g2J5L8q4)9J5k6r3u0#2N6q4)9J5k6s2y4G2L8h3g2G2L8X3g2Q4x3X3c8T1k6h3q4@1i4K6u0V1L8h3g2Q4x3X3c8@1L8#2)9J5k6r3W2@1i4K6u0V1j5g2)9J5k6s2y4W2j5%4g2J5K9i4c8&6i4K6u0V1M7X3g2K6k6h3q4J5j5$3S2Q4x3X3c8B7L8%4g2J5L8X3g2&6i4K6u0r3y4U0M7`. I Found a Real CVE in curl but Someone Beat Me to It
This article might sound a bit strange to write.
Because what I'm going to talk about is a vulnerability I discovered in curl. It's a real vulnerability—there are run-capable PoCs, Kerberos certification, everything.
Then I want to tell you why I still didn't get the CVE in the end.
But that's where things get interesting—although this time it was a "failure," I learned more from this attempt than I read 100 security research blogs.
So, this article isn't actually about a CVE, but about how I found the vulnerability, verified it, submitted it, and finally found out someone had already submitted before me.
If you've always wanted to start CVE mining but don't know where to start, this article might be for you.
If you've tried before and then "failed," it's the same.
Let's get started.
When I first started watching Curl, I had no idea what I should be looking for.
Everyone says "read the code, then find the bug," but few tell you:
Where should you look?
What kind of code patterns are worth noting?
At that time, I kept thinking:
Should I randomly open a few files and keep reading them until something suddenly looks off?
It feels completely unrealistic.
Later, I realized that a more practical approach is to start with the old vulnerability model.
See where things went wrong before.
Then ask yourself:
Could similar code logic exist elsewhere?
That's how I started this time.
I began analyzing curl's CVE history.
There are 188 historical loopholes in 25 years.
One thing kept coming up repeatedly:
Connection pool logic issues.
Once.
Twice.
Once again.
Here are a few examples:
This pattern is actually quite obvious.
curl 有一个 connection pool。为了提高效率,已经建立的连接会被重复使用。
但问题是,有时候代码没有正确检查:
旧连接上的认证状态,是否真的可以被新的请求继续使用?
这时候我就想到了:
如果 NTLM 已经因为类似的问题出过几次,那么 Kerberos 呢?
当时我下载的是 curl 8.5.0 的源码,然后开始往里面看。
连接池相关逻辑主要在:
lib/conncache.c
以及:
lib/url.c
里面有一个很关键的函数:
ConnectionExists()
它负责判断一个已经存在的连接能不能直接复用,还是需要重新创建连接。
然后我发现了几个比较有意思的地方。
文件:
lib/conncache.c
大约 126-150 行。
connection pool 使用 hash table。
而 hash key 非常简单:
port + hostname
没有用户名。
没有密码。
也没有其他认证信息。
也就是说:
example.com:443
的所有连接都会进入同一个 bucket,不管是谁发起的请求。
不过到这里还不能直接说有漏洞。
因为 hash key 只是用来做分组,真正的检查应该发生在 里面。ConnectionExists()
文件:
lib/url.c
大约 1133-1143 行。
这里其实是有 credential check 的。
但是只有当协议没有设置:
PROTOPT_CREDSPERREQUEST
这个 flag 时,才会执行。
然后我去看了 。lib/http.c
HTTP handler 都设置了这个 flag。
也就是说:
HTTP/HTTPS 的 credential check 会直接跳过。
你可能会想:
这不就是漏洞了吗?
其实不是。
原因在于 HTTP 的认证信息通常是放在每一个 HTTP request 的 header 里的。
Basic Auth、Digest Auth、Bearer Token 等,都属于 request-level authentication。
所以连接本身并不需要绑定某一个用户的认证状态。
但 Kerberos 不一样。
Kerberos,也就是常见的 Negotiate / SPNEGO,情况完全不同。
认证过程会建立一个 GSS security context。
这个 context 会存在于连接上。
后面的请求可以继续使用这个已经建立的认证上下文。
于是问题来了。
假设:
Alice
先使用 Kerberos 在 Connection #0 上完成认证。
然后:
Bob
的请求又复用了 Connection #0。
那么 Bob 的请求实际上到达的是一个:
已经以 Alice 身份完成认证的连接。
服务器看到的还是那个已经建立好的 GSS context。
这就很有意思了。
这里才真正让我觉得有问题。
文件:
lib/url.c
大约 1214-1273 行。
NTLM 有专门的保护逻辑。
它会检查:
如果不匹配,连接就不会被复用。
于是我开始搜索 Kerberos 有没有类似的检查。
结果:
没有。
一个匹配都没有。
没有 protection block。
没有 state check。
但 Kerberos 的 state 明明存在:
也就是说:
状态字段存在。
但是:
ConnectionExists()
根本没有检查它们。
这就是我当时发现的 bug。
发现代码逻辑有问题是一回事。
证明它真的可以被利用,是另外一回事。
所以我开始搭建一个可以实际验证的 PoC。
而这意味着:
得先搞一个 Kerberos 环境。
我在 Linux 上安装了 Kerberos KDC(Key Distribution Center)。
然后创建两个测试用户:
Alice 和 Bob 是两个不同的用户。
如果 curl 工作正常,他们应该使用不同的连接。
如果存在我怀疑的问题,那么 Bob 就有可能复用 Alice 已经建立的连接。
接下来需要一个支持 Kerberos 认证的服务器。
而且这个服务器还得记录:
哪个用户在什么 TCP connection 上完成了认证。
所以我用 Python 和 写了一个简单的测试服务器:gssapi
如果连接被复用,服务器就会记录这个连接之前留下来的 GSS context。
测试过程大概是这样:
然后我看到了:
当时我的第一反应就是:
真的复用了。
curl 的 verbose output 直接显示:
Re-using existing connection
测试服务器也记录到了连接上的 GSS context。
到这里我其实挺兴奋的。
因为我认为自己找到了一个真实漏洞。
有 PoC。
有代码分析。
有 root cause。
也有完整的 exploitation flow。
我把这些东西都整理好了。
但就在这个时候,我注意到了 curl 输出中的一句话:
等等。
Cleanup?
清理什么?
我重新回到了源码。
文件:
lib/http_negotiate.c
大约 140-152 行。
原来这里确实有一个 mitigation。
当 curl 在连接上使用 Kerberos state 时,会根据原来的 handshake 是否需要多个 roundtrip 来决定认证状态是否继续保留。
简单来说:
所以这个问题并不是在所有 Kerberos 环境下都完全一样。
MIT Kerberos 的默认配置下存在一定程度的缓解。
但在 Windows AD 环境中,多轮认证的情况下,问题依然存在。
而且还有一个更重要的问题:
这个 cleanup 所在的位置其实不对。
它位于:
Curl_output_negotiate()
也就是 output layer。
而不是:
ConnectionExists()
也就是 connection matching layer。
换句话说:
连接已经被匹配并复用了,之后才进行 cleanup。
NTLM 在两个层面都有保护。
Kerberos 当时没有类似的 connection matching protection。
所以问题依然存在,只是比我最开始认为的更加复杂。
curl 有 HackerOne 项目。
所以我把完整报告提交了上去:
然后点击:
Submit。
两分钟之后,curl 的作者 Daniel Stenberg 回复了。
"Thank you for your report! We will investigate and get back to you within 24 hours."
我当时真的挺激动。
然后他问了一个问题:
"Please confirm that this is still true on curl 8.20.0."