GHSA-h7gc-w2gg-p9xp
CVE Information
Summary
An inverted comparison in the OpenSSL TLS backend causes IP SubjectAltName (SAN) verification to accept certificates with mismatched IP addresses and reject certificates with correct IP addresses. This allows a network attacker with a valid CA-signed certificate containing any IP SAN to perform MITM attacks against libgit2 clients connecting to IP-literal HTTPS URLs.
Details
The
verify_server_cert()function insrc/libgit2/streams/openssl.cuses!!memcmp()to compare IP SubjectAltName entries:} else if (type == GEN_IPADD) { /* Here name isn't so much a name but a binary representation of the IP */ matched = addr && !!memcmp(name, addr, namelen); }Since
memcmp()returns 0 for matching buffers and non-zero for mismatches, applying!!inverts the logic:
- Matching IPs:
memcmp()returns 0 →!!0= 0 →matched = 0→ certificate REJECTED- Mismatched IPs:
memcmp()returns non-zero →!!n= 1 →matched = 1→ certificate ACCEPTEDThis is the opposite of the intended behavior. The subsequent control flow at lines 437-441 then incorrectly accepts mismatched certificates (
matched == 1leads togoto cleanup) and rejects matching certificates (matched == 0leads togoto cert_fail_name).Conditions for exploitation: - Target must use libgit2 built with OpenSSL (common on Linux/Unix) - Connection must be to an IP-literal URL (e.g.,
https://203.0.113.10/repo.git) - Attacker must have a valid CA-trusted certificate containing any IP SAN (not necessarily the target IP) - Attacker must be in a network position to intercept traffic (MITM)Additional impact: The inversion also causes certificates with the correct IP SAN to be rejected, meaning legitimate connections to IP-literal URLs with properly configured certificates would fail.
PoC
- Generate a CA and server certificate with IP SAN 192.0.2.1 (wrong IP)
- Start an HTTPS server on 127.0.0.1 using this certificate
- Build libgit2 with OpenSSL and attempt to connect to
https://127.0.0.1/repo.git- Observe that the certificate with wrong IP (192.0.2.1) is incorrectly accepted
Closing
Please let me know what you think. I am happy to supply PoC upon request including a fix and/or test patch.
Impact
This is a certificate validation bypass vulnerability. It affects any application using libgit2 with OpenSSL that connects to IP-literal HTTPS URLs. A network attacker (MITM position) with a valid CA-signed certificate containing any IP SAN can intercept and modify git traffic, potentially leading to:
- Code injection via malicious repository content
- Credential theft if authentication is performed over the connection
- Supply chain attacks if used in CI/CD pipelines
The vulnerability is mitigated by the following factors: - Only affects IP-literal URLs (domain names use DNS SAN verification which is correct) - Only affects OpenSSL builds (macOS SecureTransport, Windows Schannel/WinHTTP, and mbedTLS are not affected) - Attacker still needs a valid CA-trusted certificate with an IP SAN
suggested fix patch (tested, removes the vulnerability):
From: Pavel Kohout, Aisle Research, www.aisle.com Subject: [PATCH] Fix inverted IP SubjectAltName comparison in OpenSSL backend The verify_server_cert() function in the OpenSSL TLS backend incorrectly uses !!memcmp() to compare IP SubjectAltName entries. Since memcmp() returns 0 for matching buffers and non-zero for mismatches, the !! operator inverts the logic: matching IPs result in matched=0 (rejected) while mismatched IPs result in matched=1 (accepted). This allows a MITM attacker with a valid CA-signed certificate containing any IP SAN to bypass hostname verification for IP-literal HTTPS URLs. Reported-by: Pavel Kohout, Aisle Research, www.aisle.com --- diff --git a/src/libgit2/streams/openssl.c b/src/libgit2/streams/openssl.c index f12b699..c97a14f 100644 --- a/src/libgit2/streams/openssl.c +++ b/src/libgit2/streams/openssl.c @@ -379,6 +379,7 @@ static int verify_server_cert(SSL *ssl, const char *host) struct in6_addr addr6; struct in_addr addr4; void *addr = NULL; + size_t addrlen = 0; int i = -1, j, error = 0; if (SSL_get_verify_result(ssl) != X509_V_OK) { @@ -390,10 +391,12 @@ static int verify_server_cert(SSL *ssl, const char *host) if (p_inet_pton(AF_INET, host, &addr4)) { type = GEN_IPADD; addr = &addr4; + addrlen = sizeof(addr4); } else { if (p_inet_pton(AF_INET6, host, &addr6)) { type = GEN_IPADD; addr = &addr6; + addrlen = sizeof(addr6); } } @@ -428,7 +431,7 @@ static int verify_server_cert(SSL *ssl, const char *host) matched = !!check_host_name(host, name); } else if (type == GEN_IPADD) { /* Here name isn't so much a name but a binary representation of the IP */ - matched = addr && !!memcmp(name, addr, namelen); + matched = (addr && namelen == addrlen && memcmp(name, addr, namelen) == 0); } } }