Closed
Description
Affects: 6.1.13
if a client sends a malformed origin header in a CORS request to a spring boot application that looks like this:
curl 'http://localhost/sample \
-X 'OPTIONS' \
-H 'Origin: https://*@:;' \
The following exception will be thrown:
j.l.IllegalArgumentException: [https://*@:;] is not a valid HTTP URL
at o.s.w.u.UriComponentsBuilder.checkSchemeAndHost(UriComponentsBuilder.java:309)
at o.s.w.u.UriComponentsBuilder.fromOriginHeader(UriComponentsBuilder.java:371)
at o.s.w.cors.CorsUtils.isCorsRequest(CorsUtils.java:46)
at o.s.w.c.DefaultCorsProcessor.processRequest(DefaultCorsProcessor.java:86)
This exception is not handled, and bubbles out as a 500 Internal Server Error.
I would expect that the framework would handle the invalid input and reject the request with a 403 Forbidden with message "invalid cors request", like it does for many other kinds of invalid input.
The only workaround I have found is to register a custom corsFilter
bean, with a custom CorsProcessor
that handles the exception and rejects it.
Here's a unit test that fails due to this issue:
package testing;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsProcessor;
import org.springframework.web.cors.DefaultCorsProcessor;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class SafeCorsProcessorTest {
private final CorsConfiguration corsConfiguration = new CorsConfiguration();
private final CorsProcessor processor = new DefaultCorsProcessor();
private final MockHttpServletResponse response = new MockHttpServletResponse();
@BeforeEach
void setUp() {
response.reset();
}
@Test
void processRequest() throws IOException {
// Given a valid OPTIONS request.
var request = new MockHttpServletRequest();
request.addHeader(HttpHeaders.ORIGIN, "http://localhost");
request.setMethod(HttpMethod.OPTIONS.name());
// When processRequest is called
var result = processor.processRequest(corsConfiguration, request, response);
// Then the result is true and the status code is 200 OK.
assertTrue(result);
assertEquals(HttpStatus.OK.value(), response.getStatus());
}
@Test
void processRequestInvalidOrigin() throws IOException {
// Given an OPTIONS request with invalid origin header.
var request = new MockHttpServletRequest();
request.addHeader(HttpHeaders.ORIGIN, "https://*@:;");
request.setMethod(HttpMethod.OPTIONS.name());
// WHen processRequest is called
var result = processor.processRequest(corsConfiguration, request, response);
// Then the result is false and status code is 403 Forbidden,
assertFalse(result);
assertEquals(HttpStatus.FORBIDDEN.value(), response.getStatus());
}
}