host header parse error (#4083)

* adds test coverage for bug
* corrects root issue and covers the bug

Signed-off-by: Justin Florentine <justin+github@florentine.us>
pull/4093/head
Justin Florentine 2 years ago committed by GitHub
parent 750580dcca
commit e97ed8d944
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcService.java
  2. 136
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/JsonRpcJWTTest.java

@ -604,10 +604,11 @@ public class JsonRpcService {
final Iterable<String> splitHostHeader = Splitter.on(':').split(hostname);
final long hostPieces = stream(splitHostHeader).count();
// If the host contains a colon, verify the host is correctly formed - host [ ":" port ]
if (hostPieces > 2 || !Iterables.get(splitHostHeader, 1).matches("\\d{1,5}+")) {
return Optional.empty();
if (hostPieces > 1) {
if (hostPieces > 2 || !Iterables.get(splitHostHeader, 1).matches("\\d{1,5}+")) {
return Optional.empty();
}
}
return Optional.ofNullable(Iterables.get(splitHostHeader, 0));
}

@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.websocket;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.Mockito.spy;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcService;
@ -44,9 +45,13 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.WebSocket;
import io.vertx.core.http.WebSocketConnectOptions;
import io.vertx.core.json.Json;
@ -216,6 +221,7 @@ public class JsonRpcJWTTest {
wsOpts.setURI("/");
wsOpts.addHeader(
"Authorization", "Bearer " + ((EngineAuthService) jwtAuth.get()).createToken());
wsOpts.addHeader(HttpHeaders.HOST, "anything");
final Async async = context.async();
httpClient.webSocket(
@ -246,6 +252,136 @@ public class JsonRpcJWTTest {
httpClient.close();
}
@Test
public void wsRequestFromBadHostAndValidJWTIsDenied(final TestContext context) {
JsonRpcConfiguration strictHost = JsonRpcConfiguration.createEngineDefault();
strictHost.setHostsAllowlist(List.of("localhost"));
strictHost.setPort(0);
try {
strictHost.setAuthenticationPublicKeyFile(
new File(this.getClass().getResource("jwt.hex").toURI()));
} catch (URISyntaxException e) {
fail("didn't parse jwt");
}
JsonRpcService jsonRpcService =
spy(
new JsonRpcService(
vertx,
bufferDir,
strictHost,
new NoOpMetricsSystem(),
new NatService(Optional.empty(), true),
websocketMethods,
Optional.empty(),
scheduler,
jwtAuth,
healthy,
healthy));
jsonRpcService.start().join();
final InetSocketAddress inetSocketAddress = jsonRpcService.socketAddress();
int listenPort = inetSocketAddress.getPort();
final HttpClientOptions httpClientOptions =
new HttpClientOptions().setDefaultHost(HOSTNAME).setDefaultPort(listenPort);
httpClient = vertx.createHttpClient(httpClientOptions);
WebSocketConnectOptions wsOpts = new WebSocketConnectOptions();
wsOpts.setPort(listenPort);
wsOpts.setHost(HOSTNAME);
wsOpts.setURI("/");
wsOpts.addHeader(
"Authorization", "Bearer " + ((EngineAuthService) jwtAuth.get()).createToken());
wsOpts.addHeader(HttpHeaders.HOST, "bogushost");
final Async async = context.async();
httpClient.webSocket(
wsOpts,
connected -> {
if (connected.failed()) {
connected.cause().printStackTrace();
}
assertThat(connected.succeeded()).isFalse();
async.complete();
});
async.awaitSuccess(10000);
jsonRpcService.stop();
httpClient.close();
}
@Test
public void httpRequestFromBadHostAndValidJWTIsDenied(final TestContext context) {
JsonRpcConfiguration strictHost = JsonRpcConfiguration.createEngineDefault();
strictHost.setHostsAllowlist(List.of("localhost"));
strictHost.setPort(0);
try {
strictHost.setAuthenticationPublicKeyFile(
new File(this.getClass().getResource("jwt.hex").toURI()));
} catch (URISyntaxException e) {
fail("didn't parse jwt");
}
JsonRpcService jsonRpcService =
spy(
new JsonRpcService(
vertx,
bufferDir,
strictHost,
new NoOpMetricsSystem(),
new NatService(Optional.empty(), true),
websocketMethods,
Optional.empty(),
scheduler,
jwtAuth,
healthy,
healthy));
jsonRpcService.start().join();
final InetSocketAddress inetSocketAddress = jsonRpcService.socketAddress();
int listenPort = inetSocketAddress.getPort();
final HttpClientOptions httpClientOptions =
new HttpClientOptions().setDefaultHost(HOSTNAME).setDefaultPort(listenPort);
httpClient = vertx.createHttpClient(httpClientOptions);
MultiMap headers =
HttpHeaders.set(
"Authorization", "Bearer " + ((EngineAuthService) jwtAuth.get()).createToken())
.set(HttpHeaders.HOST, "bogushost");
final Async async = context.async();
httpClient.request(
HttpMethod.GET,
"/",
connected -> {
if (connected.failed()) {
connected.cause().printStackTrace();
}
HttpClientRequest request = connected.result();
request.headers().addAll(headers);
request.send(
response -> {
assertThat(response.result().statusCode()).isNotEqualTo(500);
assertThat(response.result().statusCode()).isEqualTo(403);
async.complete();
});
});
async.awaitSuccess(10000);
jsonRpcService.stop();
httpClient.close();
}
@Test
public void httpRequestWithDefaultHeaderAndInvalidJWTIsDenied(final TestContext context) {

Loading…
Cancel
Save