GHSA-RQF5-2WXV-RJF4

Vulnerability from github – Published: 2026-10-08 16:23 – Updated: 2026-10-08 16:23
VLAI
Summary
AsyncHttpClient: Digest challenge without a usable nonce downgrades to Basic and sends the password in cleartext
Details

Impact

A Digest challenge that does not yield a usable nonce is treated as a Basic challenge. The client then answers it with Authorization: Basic, which carries the username and password base64 encoded and so recoverable by anyone who sees the request.

Any of these is enough, sent by whoever writes the 401 or 407:

WWW-Authenticate: Digest realm="protected"
WWW-Authenticate: Digest realm="protected", nonce=""

The challenge announces itself as Digest, so it passes the check that would otherwise reject a non-Digest challenge for a Digest realm. It then fails to produce a nonce, and the absence of a nonce is what selected Basic. A server offering plain Basic to a Digest realm gets nothing, so labelling the challenge Digest and leaving the nonce out is what makes the difference.

This is reachable by anyone who can write the challenge: a malicious or compromised origin, a proxy, or an attacker on a plaintext hop. Digest exists precisely to keep the password off those hops. A server that stores only HA1, and anyone impersonating the real origin, does not otherwise learn the password itself, so the disclosure enables reuse against other services.

Both the target and the proxy paths are affected, through parseWWWAuthenticateHeader and parseProxyAuthenticateHeader.

Affected versions

  • 3.x: up to and including 3.0.12
  • 2.x: up to and including 2.16.0

This is long-standing behaviour on both lines, not a recent regression. On 3.0.12 there is one additional way to reach it: a quoted parameter ending in an unescaped backslash, such as realm="C:\", makes the closing quote read as escaped so the nonce is never found. That trigger is specific to 3.0.12. The downgrade it led to is fixed, but the nonce is still lost on such a challenge: every conformant reader treats the closing quote as escaped. The exchange now fails instead of falling back to Basic.

Patches

Fixed in 3.0.13 on the 3.x line and in 2.16.1 on the 2.x line. The scheme now comes from the challenge itself instead of being inferred from whether a nonce was found, so a Digest challenge stays Digest even when it cannot be read. With no nonce it produces no Authorization header at all, which fails the exchange rather than downgrading it.

The 3.x fix also corrects quoted-pair handling so that a backslash before any character other than a quote or another backslash is kept as written, matching Apache HttpComponents. That repairs a 3.0.12 regression where an unescaped realm such as DOMAIN\Users was corrupted to DOMAINUsers and failed every digest comparison. This is a deliberate deviation from a strict reading of RFC 9110 Section 5.6.4, under which a backslash before any character is a quoted-pair. Reading it strictly is what corrupts the unescaped spelling that servers actually send, and the decoding is not reversible either way, so the client follows the same rule as Apache HttpComponents.

Workarounds

No configuration prevents this. Until you can upgrade, do not use Digest authentication against a server you do not control, and do not use it over plaintext HTTP.

Details

Realm.Builder.parseWWWAuthenticateHeader and parseProxyAuthenticateHeader selected the scheme with isNonEmpty(nonce) ? AuthScheme.DIGEST : AuthScheme.BASIC, so any challenge that produced no nonce became a Basic challenge and Unauthorized401Interceptor resent the request using computeBasicAuthentication.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 3.0.12"
      },
      "package": {
        "ecosystem": "Maven",
        "name": "org.asynchttpclient:async-http-client"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.0.0.Beta1"
            },
            {
              "fixed": "3.0.13"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 2.16.0"
      },
      "package": {
        "ecosystem": "Maven",
        "name": "org.asynchttpclient:async-http-client"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.0.0"
            },
            {
              "fixed": "2.16.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-107231"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-319",
      "CWE-522",
      "CWE-757"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-08T16:23:36Z",
    "nvd_published_at": "2026-10-07T22:17:03Z",
    "severity": "HIGH"
  },
  "details": "### Impact\nA Digest challenge that does not yield a usable nonce is treated as a Basic challenge. The client then answers it with `Authorization: Basic`, which carries the username and password base64 encoded and so recoverable by anyone who sees the request.\n\nAny of these is enough, sent by whoever writes the 401 or 407:\n\n```\nWWW-Authenticate: Digest realm=\"protected\"\nWWW-Authenticate: Digest realm=\"protected\", nonce=\"\"\n```\n\nThe challenge announces itself as Digest, so it passes the check that would otherwise reject a non-Digest challenge for a Digest realm. It then fails to produce a nonce, and the absence of a nonce is what selected Basic. A server offering plain `Basic` to a Digest realm gets nothing, so labelling the challenge Digest and leaving the nonce out is what makes the difference.\n\nThis is reachable by anyone who can write the challenge: a malicious or compromised origin, a proxy, or an attacker on a plaintext hop. Digest exists precisely to keep the password off those hops. A server that stores only HA1, and anyone impersonating the real origin, does not otherwise learn the password itself, so the disclosure enables reuse against other services.\n\nBoth the target and the proxy paths are affected, through `parseWWWAuthenticateHeader` and `parseProxyAuthenticateHeader`.\n\n### Affected versions\n* 3.x: up to and including 3.0.12\n* 2.x: up to and including 2.16.0\n\nThis is long-standing behaviour on both lines, not a recent regression. On 3.0.12 there is one additional way to reach it: a quoted parameter ending in an unescaped backslash, such as `realm=\"C:\\\"`, makes the closing quote read as escaped so the nonce is never found. That trigger is specific to 3.0.12. The downgrade it led to is fixed, but the nonce is still lost on such a challenge: every conformant reader treats the closing quote as escaped. The exchange now fails instead of falling back to Basic.\n\n### Patches\nFixed in 3.0.13 on the 3.x line and in 2.16.1 on the 2.x line. The scheme now comes from the challenge itself instead of being inferred from whether a nonce was found, so a Digest challenge stays Digest even when it cannot be read. With no nonce it produces no Authorization header at all, which fails the exchange rather than downgrading it.\n\nThe 3.x fix also corrects quoted-pair handling so that a backslash before any character other than a quote or another backslash is kept as written, matching Apache HttpComponents. That repairs a 3.0.12 regression where an unescaped realm such as `DOMAIN\\Users` was corrupted to `DOMAINUsers` and failed every digest comparison. This is a deliberate deviation from a strict reading of RFC 9110 Section 5.6.4, under which a backslash before any character is a quoted-pair. Reading it strictly is what corrupts the unescaped spelling that servers actually send, and the decoding is not reversible either way, so the client follows the same rule as Apache HttpComponents.\n\n### Workarounds\nNo configuration prevents this. Until you can upgrade, do not use Digest authentication against a server you do not control, and do not use it over plaintext HTTP.\n\n### Details\n`Realm.Builder.parseWWWAuthenticateHeader` and `parseProxyAuthenticateHeader` selected the scheme with `isNonEmpty(nonce) ? AuthScheme.DIGEST : AuthScheme.BASIC`, so any challenge that produced no nonce became a Basic challenge and `Unauthorized401Interceptor` resent the request using `computeBasicAuthentication`.",
  "id": "GHSA-rqf5-2wxv-rjf4",
  "modified": "2026-10-08T16:23:36Z",
  "published": "2026-10-08T16:23:36Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/AsyncHttpClient/async-http-client/security/advisories/GHSA-rqf5-2wxv-rjf4"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-107231"
    },
    {
      "type": "WEB",
      "url": "https://github.com/AsyncHttpClient/async-http-client/commit/8376866aa9b5a7653ad19db9d472692f875caa83"
    },
    {
      "type": "WEB",
      "url": "https://github.com/AsyncHttpClient/async-http-client/commit/c8d639bf6ac341d377d610a93570bcd15565f1a6"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/AsyncHttpClient/async-http-client"
    },
    {
      "type": "WEB",
      "url": "https://github.com/AsyncHttpClient/async-http-client/releases/tag/async-http-client-project-2.16.1"
    },
    {
      "type": "WEB",
      "url": "https://github.com/AsyncHttpClient/async-http-client/releases/tag/async-http-client-project-3.0.13"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "AsyncHttpClient: Digest challenge without a usable nonce downgrades to Basic and sends the password in cleartext"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…