Lucene search

K
hackeroneKurohiroH1:2669852
HistoryAug 19, 2024 - 5:31 p.m.

curl: CVE-2024-8096: OCSP stapling bypass with GnuTLS

2024-08-1917:31:44
kurohiro
hackerone.com
10
cve-2024-8096
ocsp stapling
gnutls
certificate verification
tls backend
security issue
curl
connection security

CVSS3

6.5

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

LOW

Integrity Impact

LOW

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N

AI Score

6.5

Confidence

High

EPSS

0

Percentile

16.4%

Summary:

When the TLS backend is GnuTLS, there is an issue with the OCSP stapling validation process. As a result, even if the certificate is revoked, the connection can be established without resulting in an error.

When the OCSP stapling status response is “revoked,” gnutls_certificate_verify_peers2() returns an error. However, gnutls_certificate_verify_peers2() only returns an error when the OCSP status is “revoked.” For other statuses, gnutls_certificate_verify_peers2() returns a successful result.

In curl, the verification of the OCSP stapling status response is performed not only with the above function but also with gnutls_ocsp_status_request_is_checked(). However, this function returns a non-zero value if the OCSP stapling status response exists. As a result, if any response exists, it is treated as a successful case, and the verification process concludes.

  if(config->verifystatus) {
    if(gnutls_ocsp_status_request_is_checked(session, 0) == 0) {
      gnutls_datum_t status_request;
      gnutls_ocsp_resp_t ocsp_resp;

      gnutls_ocsp_cert_status_t status;
      gnutls_x509_crl_reason_t reason;

      rc = gnutls_ocsp_status_request_get(session, &status_request);

      infof(data, " server certificate status verification FAILED");

      if(rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
        failf(data, "No OCSP response received");
        return CURLE_SSL_INVALIDCERTSTATUS;
      }

      if(rc < 0) {
        failf(data, "Invalid OCSP response received");
        return CURLE_SSL_INVALIDCERTSTATUS;
      }

      gnutls_ocsp_resp_init(&ocsp_resp);

      rc = gnutls_ocsp_resp_import(ocsp_resp, &status_request);
      if(rc < 0) {
        failf(data, "Invalid OCSP response received");
        return CURLE_SSL_INVALIDCERTSTATUS;
      }

      (void)gnutls_ocsp_resp_get_single(ocsp_resp, 0, NULL, NULL, NULL, NULL,
                                        &status, NULL, NULL, NULL, &reason);

      switch(status) {
      case GNUTLS_OCSP_CERT_GOOD:
        break;

      case GNUTLS_OCSP_CERT_REVOKED: {
        const char *crl_reason;

        switch(reason) {
          default:
          case GNUTLS_X509_CRLREASON_UNSPECIFIED:
            crl_reason = "unspecified reason";
            break;

          case GNUTLS_X509_CRLREASON_KEYCOMPROMISE:
            crl_reason = "private key compromised";
            break;

          case GNUTLS_X509_CRLREASON_CACOMPROMISE:
            crl_reason = "CA compromised";
            break;

          case GNUTLS_X509_CRLREASON_AFFILIATIONCHANGED:
            crl_reason = "affiliation has changed";
            break;

          case GNUTLS_X509_CRLREASON_SUPERSEDED:
            crl_reason = "certificate superseded";
            break;

          case GNUTLS_X509_CRLREASON_CESSATIONOFOPERATION:
            crl_reason = "operation has ceased";
            break;

          case GNUTLS_X509_CRLREASON_CERTIFICATEHOLD:
            crl_reason = "certificate is on hold";
            break;

          case GNUTLS_X509_CRLREASON_REMOVEFROMCRL:
            crl_reason = "will be removed from delta CRL";
            break;

          case GNUTLS_X509_CRLREASON_PRIVILEGEWITHDRAWN:
            crl_reason = "privilege withdrawn";
            break;

          case GNUTLS_X509_CRLREASON_AACOMPROMISE:
            crl_reason = "AA compromised";
            break;
        }

        failf(data, "Server certificate was revoked: %s", crl_reason);
        break;
      }

      default:
      case GNUTLS_OCSP_CERT_UNKNOWN:
        failf(data, "Server certificate status is unknown");
        break;
      }

      gnutls_ocsp_resp_deinit(ocsp_resp);

      return CURLE_SSL_INVALIDCERTSTATUS;
    }
    else
      infof(data, "  server certificate status verification OK");
  }
  else
    infof(data, "  server certificate status verification SKIPPED");

Steps To Reproduce:

I have set up a test site, so please try it out.
OCSP stapling status response is configured to return “unauthorized (6).”

  1. Prepare curl with GnuTLS backend.
  2. curl https://ocsp4test.sytes.net:4433 --cert-status

An error will occur if the TLS backend is OpenSSL.

I noticed while researching that starting from GnuTLS 3.1.2, OCSP stapling is enabled by default with gnutls_init. As a result, whether you specify --cert-status or not, the behavior remains the same (currently, in the curl source code, it is not possible to disable OCSP stapling).
https://www.gnutls.org/manual/html_node/Session-initialization.html

Impact

Bypassing OCSP verification.

CVSS3

6.5

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

LOW

Integrity Impact

LOW

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N

AI Score

6.5

Confidence

High

EPSS

0

Percentile

16.4%