GHSA-FMXF-PM6P-7XGM

Vulnerability from github – Published: 2026-05-18 16:42 – Updated: 2026-05-18 16:42
VLAI?
Summary
async-http-client: Cookie header not stripped on cross-origin redirect
Details

Summary

async-http-client leaks Cookie headers to cross-origin redirect targets. When following a redirect across a security boundary (different origin, or HTTPS→HTTP downgrade), the propagatedHeaders() method in Redirect30xInterceptor.java strips Authorization and Proxy-Authorization headers but does not strip Cookie, so session cookies and other sensitive cookie values are forwarded to the redirect target — which may be attacker-controlled.

Details

The vulnerability is in client/src/main/java/org/asynchttpclient/netty/handler/intercept/Redirect30xInterceptor.java.

The caller computes stripAuth on each redirect:

boolean sameBase    = request.getUri().isSameBase(newUri);
boolean stripAuth   = !sameBase || schemeDowngrade || stripAuthorizationOnRedirect;
// ...
requestBuilder.setHeaders(propagatedHeaders(request, realm, keepBody, stripAuth));

stripAuth is true whenever the redirect crosses an origin, downgrades the scheme, or the caller opted in via AsyncHttpClientConfig#isStripAuthorizationOnRedirect().

In the vulnerable version, propagatedHeaders() only removes Authorization and Proxy-Authorization in that branch — Cookie is left untouched:

private static HttpHeaders propagatedHeaders(Request request, Realm realm, boolean keepBody, boolean stripAuthorization) {
    HttpHeaders headers = request.getHeaders()
            .remove(HOST)
            .remove(CONTENT_LENGTH);

    if (!keepBody) {
        headers.remove(CONTENT_TYPE);
    }

    if (stripAuthorization || (realm != null && (realm.getScheme() == AuthScheme.NTLM
            || realm.getScheme() == AuthScheme.SCRAM_SHA_256))) {
        headers.remove(AUTHORIZATION)
                .remove(PROXY_AUTHORIZATION);
        // BUG: COOKIE is not removed here, so cookies leak across the security boundary.
    }
    return headers;
}

The companion test class RedirectCredentialSecurityTest covers Authorization / Proxy-Authorization stripping on cross-origin redirects and scheme downgrades, but has no coverage for Cookie, which is why the regression went unnoticed.

Proof of concept

import org.asynchttpclient.*;

AsyncHttpClient client = asyncHttpClient();

// trusted-api.com responds 302 -> https://evil.com
Request request = new RequestBuilder("GET")
        .setUrl("https://trusted-api.com/endpoint")
        .setHeader("Cookie", "session=abc123; csrf=xyz789; api_key=secret")
        .setHeader("Authorization", "Bearer token123")
        .build();

client.executeRequest(request).get();

// Request seen by evil.com after the redirect:
//   Authorization: <stripped>
//   Cookie:        session=abc123; csrf=xyz789; api_key=secret   <-- leaked

Impact

  • Session hijacking — leaked session cookies allow impersonation.
  • CSRF token theft — CSRF tokens carried in cookies are disclosed.
  • API key theft — API keys stored in cookies are disclosed.
  • Privacy — tracking identifiers leak to third-party origins.

Realistic attack paths:

  • Open-redirect in a trusted API endpoint.
  • Compromised CDN or API gateway injecting redirects.
  • MITM on a plaintext hop in the redirect chain.

Fix

Add COOKIE to the headers removed alongside AUTHORIZATION / PROXY_AUTHORIZATION on the security-boundary branch:

if (stripAuthorization) {
    headers.remove(AUTHORIZATION)
            .remove(PROXY_AUTHORIZATION)
            .remove(COOKIE);
} else if (realm != null && (realm.getScheme() == AuthScheme.NTLM
        || realm.getScheme() == AuthScheme.SCRAM_SHA_256)) {
    headers.remove(AUTHORIZATION)
            .remove(PROXY_AUTHORIZATION);
}

Note that the URI-scoped CookieStore will re-add any cookies that legitimately match the new target after propagatedHeaders returns, so legitimate cross-origin sessions tracked by the client are not broken.

Fixed in 3.0.10 and 2.15.0 by commit 3b0e3e9e.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Maven",
        "name": "org.asynchttpclient:async-http-client"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.0.0.Beta1"
            },
            {
              "fixed": "3.0.10"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Maven",
        "name": "org.asynchttpclient:async-http-client"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.0.0"
            },
            {
              "fixed": "2.15.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-45300"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-200"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-18T16:42:20Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Summary\n\nasync-http-client leaks `Cookie` headers to cross-origin redirect targets. When following a redirect across a security boundary (different origin, or HTTPS\u2192HTTP downgrade), the `propagatedHeaders()` method in `Redirect30xInterceptor.java` strips `Authorization` and `Proxy-Authorization` headers but does not strip `Cookie`, so session cookies and other sensitive cookie values are forwarded to the redirect target \u2014 which may be attacker-controlled.\n\n## Details\n\nThe vulnerability is in `client/src/main/java/org/asynchttpclient/netty/handler/intercept/Redirect30xInterceptor.java`.\n\nThe caller computes `stripAuth` on each redirect:\n\n```java\nboolean sameBase    = request.getUri().isSameBase(newUri);\nboolean stripAuth   = !sameBase || schemeDowngrade || stripAuthorizationOnRedirect;\n// ...\nrequestBuilder.setHeaders(propagatedHeaders(request, realm, keepBody, stripAuth));\n```\n\n`stripAuth` is `true` whenever the redirect crosses an origin, downgrades the scheme, or the caller opted in via `AsyncHttpClientConfig#isStripAuthorizationOnRedirect()`.\n\nIn the vulnerable version, `propagatedHeaders()` only removes `Authorization` and `Proxy-Authorization` in that branch \u2014 `Cookie` is left untouched:\n\n```java\nprivate static HttpHeaders propagatedHeaders(Request request, Realm realm, boolean keepBody, boolean stripAuthorization) {\n    HttpHeaders headers = request.getHeaders()\n            .remove(HOST)\n            .remove(CONTENT_LENGTH);\n\n    if (!keepBody) {\n        headers.remove(CONTENT_TYPE);\n    }\n\n    if (stripAuthorization || (realm != null \u0026\u0026 (realm.getScheme() == AuthScheme.NTLM\n            || realm.getScheme() == AuthScheme.SCRAM_SHA_256))) {\n        headers.remove(AUTHORIZATION)\n                .remove(PROXY_AUTHORIZATION);\n        // BUG: COOKIE is not removed here, so cookies leak across the security boundary.\n    }\n    return headers;\n}\n```\n\nThe companion test class `RedirectCredentialSecurityTest` covers `Authorization` / `Proxy-Authorization` stripping on cross-origin redirects and scheme downgrades, but has no coverage for `Cookie`, which is why the regression went unnoticed.\n\n## Proof of concept\n\n```java\nimport org.asynchttpclient.*;\n\nAsyncHttpClient client = asyncHttpClient();\n\n// trusted-api.com responds 302 -\u003e https://evil.com\nRequest request = new RequestBuilder(\"GET\")\n        .setUrl(\"https://trusted-api.com/endpoint\")\n        .setHeader(\"Cookie\", \"session=abc123; csrf=xyz789; api_key=secret\")\n        .setHeader(\"Authorization\", \"Bearer token123\")\n        .build();\n\nclient.executeRequest(request).get();\n\n// Request seen by evil.com after the redirect:\n//   Authorization: \u003cstripped\u003e\n//   Cookie:        session=abc123; csrf=xyz789; api_key=secret   \u003c-- leaked\n```\n\n## Impact\n\n- **Session hijacking** \u2014 leaked session cookies allow impersonation.\n- **CSRF token theft** \u2014 CSRF tokens carried in cookies are disclosed.\n- **API key theft** \u2014 API keys stored in cookies are disclosed.\n- **Privacy** \u2014 tracking identifiers leak to third-party origins.\n\nRealistic attack paths:\n\n- Open-redirect in a trusted API endpoint.\n- Compromised CDN or API gateway injecting redirects.\n- MITM on a plaintext hop in the redirect chain.\n\n## Fix\n\nAdd `COOKIE` to the headers removed alongside `AUTHORIZATION` / `PROXY_AUTHORIZATION` on the security-boundary branch:\n\n```java\nif (stripAuthorization) {\n    headers.remove(AUTHORIZATION)\n            .remove(PROXY_AUTHORIZATION)\n            .remove(COOKIE);\n} else if (realm != null \u0026\u0026 (realm.getScheme() == AuthScheme.NTLM\n        || realm.getScheme() == AuthScheme.SCRAM_SHA_256)) {\n    headers.remove(AUTHORIZATION)\n            .remove(PROXY_AUTHORIZATION);\n}\n```\n\nNote that the URI-scoped `CookieStore` will re-add any cookies that legitimately match the new target after `propagatedHeaders` returns, so legitimate cross-origin sessions tracked by the client are not broken.\n\nFixed in **3.0.10** and **2.15.0** by commit [`3b0e3e9e`](https://github.com/AsyncHttpClient/async-http-client/commit/3b0e3e9e).",
  "id": "GHSA-fmxf-pm6p-7xgm",
  "modified": "2026-05-18T16:42:20Z",
  "published": "2026-05-18T16:42:20Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/AsyncHttpClient/async-http-client/security/advisories/GHSA-fmxf-pm6p-7xgm"
    },
    {
      "type": "WEB",
      "url": "https://github.com/AsyncHttpClient/async-http-client/commit/3b0e3e9e"
    },
    {
      "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-3.0.10"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "async-http-client: Cookie header not stripped on cross-origin redirect"
}


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…

Detection rules are retrieved from Rulezet.

Loading…

Loading…