Skip to content

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 in src/libgit2/streams/openssl.c uses !!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 ACCEPTED

This is the opposite of the intended behavior. The subsequent control flow at lines 437-441 then incorrectly accepts mismatched certificates (matched == 1 leads to goto cleanup) and rejects matching certificates (matched == 0 leads to goto 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

  1. Generate a CA and server certificate with IP SAN 192.0.2.1 (wrong IP)
  2. Start an HTTPS server on 127.0.0.1 using this certificate
  3. Build libgit2 with OpenSSL and attempt to connect to https://127.0.0.1/repo.git
  4. 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);
          }
      }
  }