Skip to content

Commit e907ae0

Browse files
arturobernalgDaniel VEGA
andauthored
HTTPCLIENT-2383 Bypass sensitive-header check in LaxRedirectStrategy (#676)
* HTTPCLIENT-2383 Bypass sensitive-header check in LaxRedirectStrategy. Override isRedirectAllowed(...) to always return true, ensuring LaxRedirectStrategy follows redirects regardless of Authorization or other sensitive headers. * [HTTPCLIENT-2383] Make LaxRedirectStrategy to allow redirects if sensitive headers are part of the request --------- Co-authored-by: Daniel VEGA <[email protected]>
1 parent 945a75d commit e907ae0

File tree

6 files changed

+179
-7
lines changed

6 files changed

+179
-7
lines changed

httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/sync/StandardTestClientBuilder.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
4343
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
4444
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
45+
import org.apache.hc.client5.http.protocol.RedirectStrategy;
4546
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
4647
import org.apache.hc.client5.testing.SSLTestContexts;
4748
import org.apache.hc.core5.http.Header;
@@ -167,6 +168,12 @@ public TestClientBuilder setUnixDomainSocket(final Path unixDomainSocket) {
167168
return this;
168169
}
169170

171+
@Override
172+
public TestClientBuilder setRedirectStrategy(final RedirectStrategy redirectStrategy) {
173+
this.clientBuilder.setRedirectStrategy(redirectStrategy);
174+
return this;
175+
}
176+
170177
@Override
171178
public TestClient build() throws Exception {
172179
final HttpClientConnectionManager connectionManagerCopy = connectionManager != null ? connectionManager :

httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/sync/TestClientBuilder.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.apache.hc.client5.http.auth.CredentialsProvider;
3838
import org.apache.hc.client5.http.classic.ExecChainHandler;
3939
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
40+
import org.apache.hc.client5.http.protocol.RedirectStrategy;
4041
import org.apache.hc.core5.http.Header;
4142
import org.apache.hc.core5.http.HttpRequestInterceptor;
4243
import org.apache.hc.core5.http.HttpResponseInterceptor;
@@ -108,6 +109,10 @@ default TestClientBuilder setUnixDomainSocket(Path unixDomainSocket) {
108109
throw new UnsupportedOperationException("Operation not supported by " + getProtocolLevel());
109110
}
110111

112+
default TestClientBuilder setRedirectStrategy(RedirectStrategy redirectStrategy) {
113+
throw new UnsupportedOperationException("Operation not supported by " + getProtocolLevel());
114+
}
115+
111116
TestClient build() throws Exception;
112117

113118
}

httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestRedirects.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.apache.hc.client5.http.cookie.BasicCookieStore;
5151
import org.apache.hc.client5.http.cookie.CookieStore;
5252
import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;
53+
import org.apache.hc.client5.http.impl.LaxRedirectStrategy;
5354
import org.apache.hc.client5.http.impl.cookie.BasicClientCookie;
5455
import org.apache.hc.client5.http.protocol.HttpClientContext;
5556
import org.apache.hc.client5.http.protocol.RedirectLocations;
@@ -761,4 +762,57 @@ void testCrossSiteRedirectWithSensitiveHeaders(final String headerName) throws E
761762
}
762763
}
763764

765+
@ParameterizedTest(name = "{displayName}; manually added header: {0}")
766+
@ValueSource(strings = {HttpHeaders.AUTHORIZATION, HttpHeaders.COOKIE})
767+
void testCrossSiteRedirectWithSensitiveHeadersAndLaxRedirectStrategy(final String headerName) throws Exception {
768+
configureClient(builder -> builder
769+
.setRedirectStrategy(new LaxRedirectStrategy())
770+
);
771+
final URIScheme scheme = scheme();
772+
final TestServer secondServer = new TestServerBootstrap(scheme())
773+
.register("/random/*", new RandomHandler())
774+
.build();
775+
try {
776+
final InetSocketAddress address2 = secondServer.start();
777+
778+
final HttpHost redirectTarget = new HttpHost(scheme.name(), "localhost", address2.getPort());
779+
780+
configureServer(bootstrap -> bootstrap
781+
.setExchangeHandlerDecorator(requestHandler -> new RedirectingDecorator(
782+
requestHandler,
783+
requestUri -> {
784+
final URI location = new URIBuilder(requestUri)
785+
.setHttpHost(redirectTarget)
786+
.setPath("/random/100")
787+
.build();
788+
return new Redirect(HttpStatus.SC_MOVED_TEMPORARILY, location.toString());
789+
}))
790+
.register("/random/*", new RandomHandler())
791+
);
792+
793+
final HttpHost target = startServer();
794+
795+
final TestClient client = client();
796+
final HttpClientContext context = HttpClientContext.create();
797+
798+
client.execute(target,
799+
ClassicRequestBuilder.get()
800+
.setHttpHost(target)
801+
.setPath("/oldlocation")
802+
.setHeader(headerName, "custom header")
803+
.build(),
804+
context,
805+
response -> {
806+
Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
807+
EntityUtils.consume(response.getEntity());
808+
return null;
809+
});
810+
final RedirectLocations redirects = context.getRedirectLocations();
811+
Assertions.assertNotNull(redirects);
812+
Assertions.assertEquals(1, redirects.size());
813+
} finally {
814+
secondServer.shutdown(CloseMode.GRACEFUL);
815+
}
816+
}
817+
764818
}

httpclient5/src/main/java/org/apache/hc/client5/http/impl/LaxRedirectStrategy.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.apache.hc.core5.annotation.ThreadingBehavior;
3737
import org.apache.hc.core5.http.Header;
3838
import org.apache.hc.core5.http.HttpHeaders;
39+
import org.apache.hc.core5.http.HttpHost;
3940
import org.apache.hc.core5.http.HttpRequest;
4041
import org.apache.hc.core5.http.HttpResponse;
4142
import org.apache.hc.core5.http.HttpStatus;
@@ -84,6 +85,16 @@ public boolean isRedirected(final HttpRequest request, final HttpResponse respon
8485
}
8586
}
8687

88+
@Override
89+
public boolean isRedirectAllowed(
90+
final HttpHost currentTarget,
91+
final HttpHost newTarget,
92+
final HttpRequest redirect,
93+
final HttpContext context) {
94+
// Always allow, regardless of sensitive headers
95+
return true;
96+
}
97+
8798
protected boolean isRedirectable(final String method) {
8899
for (final String m : REDIRECT_METHODS) {
89100
if (m.equalsIgnoreCase(method)) {

httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestDefaultRedirectStrategy.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -261,13 +261,6 @@ void testRedirectAllowed() throws Exception {
261261
BasicRequestBuilder.get("/").build(),
262262
null));
263263

264-
Assertions.assertTrue(redirectStrategy.isRedirectAllowed(
265-
new HttpHost("somehost", 1234),
266-
new HttpHost("somehost", 1234),
267-
BasicRequestBuilder.get("/")
268-
.build(),
269-
null));
270-
271264
Assertions.assertTrue(redirectStrategy.isRedirectAllowed(
272265
new HttpHost("somehost", 1234),
273266
new HttpHost("somehost", 1234),

httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestLaxRedirectStrategy.java

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,18 @@
3232
import org.apache.hc.client5.http.classic.methods.HttpHead;
3333
import org.apache.hc.client5.http.classic.methods.HttpPost;
3434
import org.apache.hc.client5.http.classic.methods.HttpPut;
35+
import org.apache.hc.core5.http.HttpHeaders;
36+
import org.apache.hc.core5.http.HttpHost;
3537
import org.apache.hc.core5.http.HttpRequest;
3638
import org.apache.hc.core5.http.HttpResponse;
3739
import org.apache.hc.core5.http.HttpStatus;
3840
import org.apache.hc.core5.http.protocol.HttpContext;
41+
import org.apache.hc.core5.http.support.BasicRequestBuilder;
42+
import org.junit.jupiter.api.Assertions;
3943
import org.junit.jupiter.api.Test;
4044

4145
import static org.junit.jupiter.api.Assertions.assertEquals;
46+
import static org.junit.jupiter.api.Assertions.assertTrue;
4247
import static org.mockito.Mockito.mock;
4348
import static org.mockito.Mockito.when;
4449

@@ -68,6 +73,24 @@ void testIsRedirectedWithNonRedirectMethod() {
6873
testIsRedirected(new HttpPut("/put"), false);
6974
}
7075

76+
@Test
77+
void testIsRedirectAllowedAlwaysTrue() {
78+
final LaxRedirectStrategy strategy = new LaxRedirectStrategy();
79+
final HttpContext context = mock(HttpContext.class);
80+
final HttpHost current = new HttpHost("http", "localhost", 80);
81+
final HttpHost next = new HttpHost("http", "example.com", 80);
82+
// Create a request with an Authorization header
83+
final HttpRequest request = new HttpGet("/test");
84+
request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer token");
85+
86+
// Even with sensitive headers and target change, LaxRedirectStrategy should allow it
87+
assertTrue(
88+
strategy.isRedirectAllowed(current, next, request, context),
89+
"LaxRedirectStrategy should always allow redirects regardless of sensitive headers"
90+
);
91+
}
92+
93+
7194
private void testIsRedirected(final HttpRequest request, final boolean expected) {
7295
final LaxRedirectStrategy strategy = new LaxRedirectStrategy();
7396
final HttpResponse response = mock(HttpResponse.class);
@@ -81,4 +104,83 @@ private void testIsRedirected(final HttpRequest request, final boolean expected)
81104

82105
assertEquals(expected, strategy.isRedirected(request, response, context));
83106
}
107+
108+
/**
109+
* Test {@link LaxRedirectStrategy#isRedirectAllowed(HttpHost, HttpHost, HttpRequest, HttpContext)}.
110+
* The method should return true for all requests, enabling backward compatibility.
111+
*
112+
**/
113+
@Test
114+
void testRedirectAllowed() throws Exception {
115+
final LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();
116+
117+
// Same host and port
118+
Assertions.assertTrue(redirectStrategy.isRedirectAllowed(
119+
new HttpHost("somehost", 1234),
120+
new HttpHost("somehost", 1234),
121+
BasicRequestBuilder.get("/").build(),
122+
null));
123+
124+
// Same host and port with Authorization header
125+
Assertions.assertTrue(redirectStrategy.isRedirectAllowed(
126+
new HttpHost("somehost", 1234),
127+
new HttpHost("somehost", 1234),
128+
BasicRequestBuilder.get("/")
129+
.addHeader(HttpHeaders.AUTHORIZATION, "let me pass")
130+
.build(),
131+
null));
132+
133+
// Same host and port with Cookie header
134+
Assertions.assertTrue(redirectStrategy.isRedirectAllowed(
135+
new HttpHost("somehost", 1234),
136+
new HttpHost("somehost", 1234),
137+
BasicRequestBuilder.get("/")
138+
.addHeader(HttpHeaders.COOKIE, "stuff=blah")
139+
.build(),
140+
null));
141+
142+
// Different host and same port
143+
Assertions.assertTrue(redirectStrategy.isRedirectAllowed(
144+
new HttpHost("somehost", 1234),
145+
new HttpHost("someotherhost", 1234),
146+
BasicRequestBuilder.get("/")
147+
.build(),
148+
null));
149+
150+
// Different host and same port with Authorization header
151+
Assertions.assertTrue(redirectStrategy.isRedirectAllowed(
152+
new HttpHost("somehost", 1234),
153+
new HttpHost("someotherhost", 1234),
154+
BasicRequestBuilder.get("/")
155+
.addHeader(HttpHeaders.AUTHORIZATION, "let me pass")
156+
.build(),
157+
null));
158+
159+
// Different host and same port with Cookie header
160+
Assertions.assertTrue(redirectStrategy.isRedirectAllowed(
161+
new HttpHost("somehost", 1234),
162+
new HttpHost("someotherhost", 1234),
163+
BasicRequestBuilder.get("/")
164+
.addHeader(HttpHeaders.COOKIE, "stuff=blah")
165+
.build(),
166+
null));
167+
168+
// Same host and different ports with Authorization header
169+
Assertions.assertTrue(redirectStrategy.isRedirectAllowed(
170+
new HttpHost("somehost", 1234),
171+
new HttpHost("somehost", 80),
172+
BasicRequestBuilder.get("/")
173+
.addHeader(HttpHeaders.AUTHORIZATION, "let me pass")
174+
.build(),
175+
null));
176+
177+
// Same host and different ports with Cookie header
178+
Assertions.assertTrue(redirectStrategy.isRedirectAllowed(
179+
new HttpHost("somehost", 1234),
180+
new HttpHost("somehost", 80),
181+
BasicRequestBuilder.get("/")
182+
.addHeader(HttpHeaders.COOKIE, "stuff=blah")
183+
.build(),
184+
null));
185+
}
84186
}

0 commit comments

Comments
 (0)