OpenSSL后端内存泄漏漏洞分析:X509证书未释放导致DoS风险

本文详细分析了curl中OpenSSL后端的X509证书内存泄漏漏洞,该漏洞在使用协商认证的HTTPS连接时会导致每个请求泄漏内存,长期运行可能引发拒绝服务风险。

漏洞概述

在curl的OpenSSL后端中,ossl_get_channel_binding函数通过SSL_get1_peer_certificate获取服务器X509证书的新引用,但从未释放它。当使用协商(SPNEGO) over TLS时,每次触发此路径都会泄漏一个X509对象。在长时间运行的libcurl客户端中,这会导致无限制的内存增长和潜在的拒绝服务。

触发条件

  • 仅在使用OpenSSL后端(>= 1.1.0)时发生
  • 当curl/libcurl使用GSSAPI构建(HAVE_GSSAPI)
  • 在HTTPS上处理协商质询时

技术细节

代码问题

lib/vtls/openssl.cossl_get_channel_binding函数中:

1
2
3
4
5
cert = SSL_get1_peer_certificate(octx->ssl);
if(!cert) {
  /* No server certificate, don't do channel binding */
  return CURLE_OK;
}

代码调用SSL_get1_peer_certificate(这会增加X509引用计数),但在所有返回路径(成功或错误)上都没有调用X509_free

影响范围

  • 需要libcurl使用OpenSSL(>=1.1.0)和GSSAPI支持构建
  • 仅影响在HTTPS上使用协商认证的客户端
  • 线性泄漏率(未放大),但随时间推移无限制

复现步骤

构建环境

1
2
3
4
./buildconf
./configure --with-ssl --with-gssapi
make -j
./src/curl -V   # 应显示OpenSSL和GSS-API/SPNEGO

测试服务器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import ssl, http.server, socketserver
class H(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(401)
        self.send_header("WWW-Authenticate", "Negotiate")
        self.send_header("Content-Length", "0")
        self.end_headers()
    def log_message(self, *args): pass
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain("/tmp/cert.pem", "/tmp/key.pem")
with socketserver.TCPServer(("127.0.0.1", 4443), H) as httpd:
    httpd.socket = ctx.wrap_socket(httpd.socket, server_side=True)
    httpd.serve_forever()

漏洞验证

使用valgrind检测可观察到约100个与X509相关的泄漏分配(每个请求一个),源自ossl_get_channel_binding中的SSL_get1_peer_certificate使用。

修复方案

建议修复方法是将SSL_get1_peer_certificate替换为SSL_get0_peer_certificate

1
2
- cert = SSL_get1_peer_certificate(octx->ssl);
+ cert = SSL_get0_peer_certificate(octx->ssl);

严重性评估

低危 - 范围有限,需要特定的构建配置和认证方法,但对受影响的部署存在真正的DoS风险。

每个新的TLS连接泄漏通常少于500字节,仅在使用协商认证的新TLS连接上发生,且使用此认证的客户端较为罕见,不太可能是极长寿命的进程。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计