Reactor Netty Credential Leak on Chained Redirects — CVE-2025-22227
Patch Available.
Overview
Reactor Netty is the reactive networking layer of Project Reactor: it wraps Netty in a Reactive Streams API and supplies the non-blocking HTTP client and server that Spring WebFlux runs on. The reactor-netty-http module carries that HTTP client, including the client's optional automatic following of HTTP redirect responses.
An Information Exposure vulnerability (CVE-2025-22227) has been identified in the redirect handling of the Reactor Netty HTTP client, which allows attackers to receive the credentials sent with the original request at a host the caller never intended to authenticate to.
Per OWASP: "The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information."
This issue affects the HTTP client redirect handling of Reactor Netty, and only when the client has been explicitly configured to follow redirects.
Details
Module Info
- Product: Reactor Netty
- Affected packages:
io.projectreactor.netty:reactor-netty-http - Affected versions: <1.2.8, >=1.3.0-M1 <1.3.0-M5
- GitHub repository: https://github.com/reactor/reactor-netty
- Published packages: https://central.sonatype.com/artifact/io.projectreactor.netty/reactor-netty-http
- Package manager: Maven
- Fixed in:
- NES for Reactor Netty 1.0.48-reactor-netty-1.0.50
- OSS Reactor Netty 1.2.8, 1.3.0-M5
Vulnerability Info
This High-severity vulnerability is found in the io.projectreactor.netty:reactor-netty-http package in the HTTP client redirect handling of Reactor Netty.
When the client follows a redirect, HttpClientHandler strips the sensitive EXPECT, COOKIE, AUTHORIZATION and PROXY_AUTHORIZATION headers from the outgoing request only when the redirect target differs from the value held in its fromURI field:
Consumer<HttpClientRequest> consumer = null;
if (fromURI != null && !toURI.equals(fromURI)) {
if (handler instanceof RedirectSendHandler) {
headers.remove(HttpHeaderNames.EXPECT)
.remove(HttpHeaderNames.COOKIE)
.remove(HttpHeaderNames.AUTHORIZATION)
.remove(HttpHeaderNames.PROXY_AUTHORIZATION);
}
else {
consumer = request ->
request.requestHeaders()
.remove(HttpHeaderNames.EXPECT)
.remove(HttpHeaderNames.COOKIE)
.remove(HttpHeaderNames.AUTHORIZATION)
.remove(HttpHeaderNames.PROXY_AUTHORIZATION);
}
}
That field does not hold the original request URI. Each time a redirect is followed, redirect() reassigns it to the hop the client is leaving, so the guard compares each hop against its immediate predecessor:
void redirect(String to) {
Supplier<String>[] redirectedFrom = this.redirectedFrom;
UriEndpoint toURITemp;
UriEndpoint from = toURI;
SocketAddress address = from.getRemoteAddress();
// resolution of the Location header omitted
fromURI = from;
toURI = toURITemp;
resourceUrl = toURITemp.toExternalForm();
this.redirectedFrom = addToRedirectedFromArray(redirectedFrom, from);
}
The comparison is address based, because UriEndpoint.equals considers only the resolved remote address and ignores the path:
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
UriEndpoint that = (UriEndpoint) o;
return getRemoteAddress().equals(that.getRemoteAddress());
}
The outgoing headers are rebuilt from the configured defaults on every hop, so the stripping decision is recomputed each time rather than persisting once it has been applied. A host that has already received a stripped request and then issues a further redirect within its own authority therefore compares equal to its predecessor, the guard does not fire, and the credentials from the original request are restored onto the rebuilt headers and sent to that host. An attacker who controls any host in a redirect chain can obtain them by redirecting once more to itself.
This vulnerability was introduced in 2019 with Reactor Netty 0.8.11.RELEASE.
Mitigation
The affected 1.0.x release line is End-of-Life and will not receive community updates addressing this issue.
Users of the affected components should apply one of the following mitigations:
- Upgrade Reactor Netty to a currently supported release containing the fix.
- Leverage a commercial support partner like HeroDevs for post-EOL security support.
Credits
- Martin van Wingerden (finder)