Missing error check and insufficient random bytes in HTTP Digest authentication for SOAP

Inhalt

This report was co-authored by @timwolla

Summary

The random byte generation function used in the SOAP HTTP Digest authentication code is not checked for failure. This can result in a stack information leak. Furthermore, there's an insufficient number of random bytes used.

Details

Context: https://github.com/php/php-src/blob/af809ef028e1e3ed59284e2506cab0f275b55d8e/ext/soap/php_http.c#L664-L671

If php_random_bytes_throw fails, the nonce will be uninitialized, but still sent to the server. The client nonce is intended to protect against a malicious server. See section 5.10 and 5.12 of RFC 7616, and bullet point 2 below.

Tim pointed out that even though it's the MD5 of the nonce that gets sent, enumerating 31 bits is trivial. So we have still a stack information leak of 31 bits.

Furthermore, Tim found the following issues:

  • The small size of cnonce might cause the server to erroneously reject a request due to a repeated (cnonce, nc) pair. As per the birthday problem 31 bits of randomness will return a duplication with 50% chance after less than 55000 requests and nc always starts counting at 1.

  • The cnonce is intended to protect the client and password against a malicious server that returns a constant server nonce where the server precomputed a rainbow table between passwords and correct client response. As storage is fairly cheap, a server could precompute the client responses for (a subset of) client nonces and still have a chance of reversing the client response with the same probability as the cnonce duplication.

    Precomputing the rainbow table for all 2^31 cnonces increases the rainbow table size by factor 2 billion, which is infeasible. But precomputing it for 2^14 cnonces only increases the table size by factor 16k and the server would still have a 10% chance of successfully reversing a password with a single client request.

PoC

We do not have a proof-of-concept.

Impact

The weak randomness affects applications that use SOAP with HTTP Digest authentication against a possibly malicious server over HTTP. The stack information leak applies to both HTTP and HTTPS, but is only a few bytes.

Proposed patch

This patch fixes the issues by increasing the nonce size, and checking the return value of php_random_bytes_throw(). In the process we also get rid of the MD5 hashing of the nonce.

From f26fcd3a152a5c6b2d140cb35a5040671696f6e1 Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
Date: Sun, 16 Apr 2023 15:05:03 +0200
Subject: [PATCH] Fix missing randomness check and insufficient random bytes
 for SOAP HTTP Digest
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

If php_random_bytes_throw fails, the nonce will be uninitialized, but
still sent to the server. The client nonce is intended to protect
against a malicious server. See section 5.10 and 5.12 of RFC 7616 [1],
and bullet point 2 below.

Tim pointed out that even though it's the MD5 of the nonce that gets sent,
enumerating 31 bits is trivial. So we have still a stack information leak
of 31 bits.

Furthermore, Tim found the following issues:
* The small size of cnonce might cause the server to erroneously reject
  a request due to a repeated (cnonce, nc) pair. As per the birthday
  problem 31 bits of randomness will return a duplication with 50%
  chance after less than 55000 requests and nc always starts counting at 1.
* The cnonce is intended to protect the client and password against a
  malicious server that returns a constant server nonce where the server
  precomputed a rainbow table between passwords and correct client response.
  As storage is fairly cheap, a server could precompute the client responses
  for (a subset of) client nonces and still have a chance of reversing the
  client response with the same probability as the cnonce duplication.

  Precomputing the rainbow table for all 2^31 cnonces increases the rainbow
  table size by factor 2 billion, which is infeasible. But precomputing it
  for 2^14 cnonces only increases the table size by factor 16k and the server
  would still have a 10% chance of successfully reversing a password with a
  single client request.

This patch fixes the issues by increasing the nonce size, and checking
the return value of php_random_bytes_throw(). In the process we also get
rid of the MD5 hashing of the nonce.

[1] RFC 7616: https://www.rfc-editor.org/rfc/rfc7616

Co-authored-by: Tim Düsterhus ---
 ext/soap/php_http.c | 21 +++++++++++++--------
 1 file changed, 13 insertions(+), 8 deletions(-)

diff --git a/ext/soap/php_http.c b/ext/soap/php_http.c
index db3c97b647..a619949c69 100644
--- a/ext/soap/php_http.c
+++ b/ext/soap/php_http.c
@@ -657,18 +657,23 @@ try_again:
 			has_authorization = 1;
 			if (Z_TYPE_P(digest) == IS_ARRAY) {
 				char          HA1[33], HA2[33], response[33], cnonce[33], nc[9];
-				zend_long     nonce;
+				unsigned char nonce[16];
 				PHP_MD5_CTX   md5ctx;
 				unsigned char hash[16];
 
-				php_random_bytes_throw(&nonce, sizeof(nonce));
-				nonce &= 0x7fffffff;
+				if (UNEXPECTED(php_random_bytes_throw(&nonce, sizeof(nonce)) != SUCCESS)) {
+					ZEND_ASSERT(EG(exception));
+					php_stream_close(stream);
+					convert_to_null(Z_CLIENT_HTTPURL_P(this_ptr));
+					convert_to_null(Z_CLIENT_HTTPSOCKET_P(this_ptr));
+					convert_to_null(Z_CLIENT_USE_PROXY_P(this_ptr));
+					smart_str_free(&soap_headers_z);
+					smart_str_free(&soap_headers);
+					return FALSE;
+				}
 
-				PHP_MD5Init(&md5ctx);
-				snprintf(cnonce, sizeof(cnonce), ZEND_LONG_FMT, nonce);
-				PHP_MD5Update(&md5ctx, (unsigned char*)cnonce, strlen(cnonce));
-				PHP_MD5Final(hash, &md5ctx);
-				make_digest(cnonce, hash);
+				php_hash_bin2hex(cnonce, nonce, sizeof(nonce));
+				cnonce[32] = 0;
 
 				if ((tmp = zend_hash_str_find(Z_ARRVAL_P(digest), "nc", sizeof("nc")-1)) != NULL &&
 					Z_TYPE_P(tmp) == IS_LONG) {
-- 
2.40.0

Verknuepfte CVEs

CVE-ID Severity (CVE.org) CVSS (CVE.org) EPSS EPSS-% Veroeffentlicht (CVE.org)

CVE-2023-3247

- - - -

Quellen-Details

Bezeichnung Name Kategorie Tags Zielgruppe Sprache Feed-URL
PHP Security (php/php-src GHSA)

php_sec

vendor_advisory php, runtime - de https://github.com/php/php-src/security/advisories