From 4104fbbfbc42173a899f4ca7ac61478a8bd1d83a Mon Sep 17 00:00:00 2001 From: Ehis Edemakhiota Date: Mon, 18 Jul 2022 12:15:28 +0100 Subject: [PATCH] Unit tests for Http3 xx status code based injection vulnerability (#374) unit tests for the Http3_xx_status_code_based_injection class --- .../internal/utility/FrameworkConstants.java | 2 + .../Http3xxStatusCodeBasedInjection.java | 20 +- src/main/resources/i18n/messages.properties | 10 +- .../resources/i18n/messages_en_US.properties | 8 +- src/main/resources/static/index.html | 6 +- .../Http3xxStatusCodeBasedInjectionTest.java | 592 ++++++++++++++++++ 6 files changed, 618 insertions(+), 20 deletions(-) create mode 100644 src/test/java/org/sasanlabs/service/vulnerability/openRedirect/Http3xxStatusCodeBasedInjectionTest.java diff --git a/src/main/java/org/sasanlabs/internal/utility/FrameworkConstants.java b/src/main/java/org/sasanlabs/internal/utility/FrameworkConstants.java index 30c180c7..44c3325e 100644 --- a/src/main/java/org/sasanlabs/internal/utility/FrameworkConstants.java +++ b/src/main/java/org/sasanlabs/internal/utility/FrameworkConstants.java @@ -19,6 +19,8 @@ public interface FrameworkConstants { String HTTP = "http://"; String HTTPS = "https://"; + + String WWW = "www."; String COLON = ":"; String SLASH = "/"; String NEXT_LINE = "\n"; diff --git a/src/main/java/org/sasanlabs/service/vulnerability/openRedirect/Http3xxStatusCodeBasedInjection.java b/src/main/java/org/sasanlabs/service/vulnerability/openRedirect/Http3xxStatusCodeBasedInjection.java index 3a4736c3..d7d796a4 100644 --- a/src/main/java/org/sasanlabs/service/vulnerability/openRedirect/Http3xxStatusCodeBasedInjection.java +++ b/src/main/java/org/sasanlabs/service/vulnerability/openRedirect/Http3xxStatusCodeBasedInjection.java @@ -81,10 +81,11 @@ public ResponseEntity getVulnerablePayloadLevel1( // Payloads: // 1. Protocol other than http can be used e.g. ftp://ftp.dlptest.com/ also // 2. "//facebook.com" + @AttackVector( vulnerabilityExposed = {VulnerabilityType.OPEN_REDIRECT_3XX_STATUS_CODE}, description = - "OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_OR_DOMAIN_IS_SAME") + "OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_WWW_OR_DOMAIN_IS_SAME") @VulnerableAppRequestMapping( value = LevelConstants.LEVEL_2, htmlTemplate = "LEVEL_1/Http3xxStatusCodeBasedInjection") @@ -96,7 +97,8 @@ public ResponseEntity getVulnerablePayloadLevel2( urlToRedirect, (url) -> (!url.startsWith(FrameworkConstants.HTTP) - && !url.startsWith(FrameworkConstants.HTTPS)) + && !url.startsWith(FrameworkConstants.HTTPS) + && !url.startsWith(FrameworkConstants.WWW)) || requestUrl.getAuthority().equals(urlToRedirect)); } @@ -106,7 +108,7 @@ public ResponseEntity getVulnerablePayloadLevel2( @AttackVector( vulnerabilityExposed = {VulnerabilityType.OPEN_REDIRECT_3XX_STATUS_CODE}, description = - "OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_//_OR_DOMAIN_IS_SAME") + "OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_WWW_//_OR_DOMAIN_IS_SAME") @VulnerableAppRequestMapping( value = LevelConstants.LEVEL_3, htmlTemplate = "LEVEL_1/Http3xxStatusCodeBasedInjection") @@ -119,7 +121,8 @@ public ResponseEntity getVulnerablePayloadLevel3( (url) -> (!url.startsWith(FrameworkConstants.HTTP) && !url.startsWith(FrameworkConstants.HTTPS) - && !url.startsWith("//")) + && !url.startsWith("//") + && !url.startsWith(FrameworkConstants.WWW)) || requestUrl.getAuthority().equals(url)); } @@ -127,7 +130,7 @@ public ResponseEntity getVulnerablePayloadLevel3( @AttackVector( vulnerabilityExposed = {VulnerabilityType.OPEN_REDIRECT_3XX_STATUS_CODE}, description = - "OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_//_NULL_BYTE_OR_DOMAIN_IS_SAME") + "OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_WWW_HTTPS_//_NULL_BYTE_OR_DOMAIN_IS_SAME") @VulnerableAppRequestMapping( value = LevelConstants.LEVEL_4, htmlTemplate = "LEVEL_1/Http3xxStatusCodeBasedInjection") @@ -140,6 +143,7 @@ public ResponseEntity getVulnerablePayloadLevel4( (url) -> (!url.startsWith(FrameworkConstants.HTTP) && !url.startsWith(FrameworkConstants.HTTPS) + && !url.startsWith(FrameworkConstants.WWW) && !url.startsWith("//") && !url.startsWith(NULL_BYTE_CHARACTER)) || requestUrl.getAuthority().equals(url)); @@ -152,7 +156,7 @@ public ResponseEntity getVulnerablePayloadLevel4( @AttackVector( vulnerabilityExposed = {VulnerabilityType.OPEN_REDIRECT_3XX_STATUS_CODE}, description = - "OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_//_%_OR_DOMAIN_IS_SAME") + "OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_//_WWW_%_OR_DOMAIN_IS_SAME") @VulnerableAppRequestMapping( value = LevelConstants.LEVEL_5, htmlTemplate = "LEVEL_1/Http3xxStatusCodeBasedInjection") @@ -166,6 +170,7 @@ public ResponseEntity getVulnerablePayloadLevel5( (!url.startsWith(FrameworkConstants.HTTP) && !url.startsWith(FrameworkConstants.HTTPS) && !url.startsWith("//") + && !url.startsWith(FrameworkConstants.WWW) && !url.startsWith(NULL_BYTE_CHARACTER) && (url.length() > 0 && url.charAt(0) > 20)) || requestUrl.getAuthority().equals(url)); @@ -225,7 +230,6 @@ public ResponseEntity getVulnerablePayloadLevel7( htmlTemplate = "LEVEL_1/Http3xxStatusCodeBasedInjection") public ResponseEntity getVulnerablePayloadLevel8( RequestEntity requestEntity, @RequestParam(RETURN_TO) String urlToRedirect) { - return this.getURLRedirectionResponseEntity( - urlToRedirect, (url) -> WHITELISTED_URLS.contains(url)); + return this.getURLRedirectionResponseEntity(urlToRedirect, WHITELISTED_URLS::contains); } } diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties index 04d449de..76d9c631 100755 --- a/src/main/resources/i18n/messages.properties +++ b/src/main/resources/i18n/messages.properties @@ -74,11 +74,11 @@ the phishing attack because many users, even if they verify these features, will Some myths: Are URL shorteners \u201Cvulnerable\u201D due to open redirects?
OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER=\"returnTo\" query parameter's value is directly added to the \"Location\" header. -OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_OR_DOMAIN_IS_SAME=\"returnTo\" query parameter's value is directly added to the \"Location\" header if it doesn't starts with \"http\" and \"https\" or domain is same as the application. -OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_//_OR_DOMAIN_IS_SAME=\"returnTo\" query parameter's value is directly added to the \"Location\" header if it doesn't starts with \"http\", \"https\" and "//" or domain is same as the application. -OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_//_NULL_BYTE_OR_DOMAIN_IS_SAME=\"returnTo\" query parameter's value is directly added to the \"Location\" header if it doesn't starts with \"http\", \"https\", "//" and Null Byte or domain is same as the application. -OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_//_%_OR_DOMAIN_IS_SAME=\"returnTo\" query parameter's value is directly added to the \"Location\" header if it doesn't starts with \"http\", \"https\", "//" and character less than ascii value 33 or domain is same as the application. -OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADDED_TO_LOCATION_HEADER_BY_ADDING_DOMAIN_AS_PREFIX=\"returnTo\" query parameter's value is directly added to the \"Location\" header by prefixing it will applications domain name. +OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_WWW_OR_DOMAIN_IS_SAME=\"returnTo\" query parameter's value is directly added to the \"Location\" header if it doesn't starts with \"http\" ,\"www\"and \"https\" or domain is same as the application. +OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_WWW_//_OR_DOMAIN_IS_SAME=\"returnTo\" query parameter's value is directly added to the \"Location\" header if it doesn't starts with \"http\", \"www\",\"https\" and "//" or domain is same as the application. +OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_WWW_HTTPS_//_NULL_BYTE_OR_DOMAIN_IS_SAME=\"returnTo\" query parameter's value is directly added to the \"Location\" header if it doesn't starts with \"http\", \"https\", \"www\","//" and Null Byte or domain is same as the application. +OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_//_WWW_%_OR_DOMAIN_IS_SAME=\"returnTo\" query parameter's value is directly added to the \"Location\" header if it doesn't starts with \"http\", \"www\", \"https\", "//" and character less than ascii value 33 or domain is same as the application. +OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADDED_TO_LOCATION_HEADER_BY_ADDING_DOMAIN_AS_PREFIX=\"returnTo\" query parameter's value is directly added to the \"Location\" header by prefixing it with application's domain name. ## Meta Tag based URL Redirection diff --git a/src/main/resources/i18n/messages_en_US.properties b/src/main/resources/i18n/messages_en_US.properties index 85fd4d5a..e5b93ed0 100755 --- a/src/main/resources/i18n/messages_en_US.properties +++ b/src/main/resources/i18n/messages_en_US.properties @@ -70,10 +70,10 @@ the phishing attack because many users, even if they verify these features, will Some myths: Are URL shorteners \u201Cvulnerable\u201D due to open redirects?
OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER=\"returnTo\" query parameter's value is directly added to the \"Location\" header. -OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_OR_DOMAIN_IS_SAME=\"returnTo\" query parameter's value is directly added to the \"Location\" header if it doesn't starts with \"http\" and \"https\" or domain is same as the application. -OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_//_OR_DOMAIN_IS_SAME=\"returnTo\" query parameter's value is directly added to the \"Location\" header if it doesn't starts with \"http\", \"https\" and "//" or domain is same as the application. -OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_//_NULL_BYTE_OR_DOMAIN_IS_SAME=\"returnTo\" query parameter's value is directly added to the \"Location\" header if it doesn't starts with \"http\", \"https\", "//" and Null Byte or domain is same as the application. -OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_//_%_OR_DOMAIN_IS_SAME=\"returnTo\" query parameter's value is directly added to the \"Location\" header if it doesn't starts with \"http\", \"https\", "//" and character less than ascii value 33 or domain is same as the application. +OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_WWW_OR_DOMAIN_IS_SAME=\"returnTo\" query parameter's value is directly added to the \"Location\" header if it doesn't starts with \"http\",\"www\" and \"https\" or domain is same as the application. +OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_WWW_//_OR_DOMAIN_IS_SAME=\"returnTo\" query parameter's value is directly added to the \"Location\" header if it doesn't starts with \"http\",\"www\", \"https\" and "//" or domain is same as the application. +OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_WWW_HTTPS_//_NULL_BYTE_OR_DOMAIN_IS_SAME=\"returnTo\" query parameter's value is directly added to the \"Location\" header if it doesn't starts with \"http\",\"www\", \"https\", "//" and Null Byte or domain is same as the application. +OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_//_WWW_%_OR_DOMAIN_IS_SAME=\"returnTo\" query parameter's value is directly added to the \"Location\" header if it doesn't starts with \"http\",\"www\", \"https\", "//" and character less than ascii value 33 or domain is same as the application. OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADDED_TO_LOCATION_HEADER_BY_ADDING_DOMAIN_AS_PREFIX=\"returnTo\" query parameter's value is directly added to the \"Location\" header by prefixing it will applications domain name. diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 55ee4b22..e4df6155 100755 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -119,16 +119,16 @@

How can Vulnerability Scanning Tools use VulnerableApp?

secure them and there are many security vulnerability finding tools but while developing those tools developers need to test them but there are no or very less such extensible vulnerable apps for testing those tools. - There are deliberately vulnerable applications exists in the market but they + There are deliberately vulnerable applications exists in the market, but they are not written with such an intent and hence lags extensibility e.g. adding new vulnerablities is quite difficult.
- So generally developer write there own vulnerable applications but that cause productivity loss and + So generally developer write their own vulnerable applications but that cause productivity loss and also many times rework is done. This Project VulnerableApp is build keeping these factors in mind so this project - is scalable, extensible, easiers to integrate and easier to learn. + is scalable, extensible, easier to integrate and easier to learn.
--> diff --git a/src/test/java/org/sasanlabs/service/vulnerability/openRedirect/Http3xxStatusCodeBasedInjectionTest.java b/src/test/java/org/sasanlabs/service/vulnerability/openRedirect/Http3xxStatusCodeBasedInjectionTest.java new file mode 100644 index 00000000..dd49fdd6 --- /dev/null +++ b/src/test/java/org/sasanlabs/service/vulnerability/openRedirect/Http3xxStatusCodeBasedInjectionTest.java @@ -0,0 +1,592 @@ +package org.sasanlabs.service.vulnerability.openRedirect; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.sasanlabs.vulnerability.utils.Constants; +import org.springframework.http.*; + +class Http3xxStatusCodeBasedInjectionTest { + private Http3xxStatusCodeBasedInjection http3xxStatusCodeBasedInjection; + private static final String LOCATION_HEADER_KEY = "Location"; + + @BeforeEach + void setUp() { + http3xxStatusCodeBasedInjection = Mockito.spy(new Http3xxStatusCodeBasedInjection()); + } + + @Test + @DisplayName( + "Level 1- test that returnTo query parameter's value is directly added to the Location header") + void test_That_ReturnToQueryParameterValue_IsAddedToLocationHeader_Level1() { + String redirectUrl = "https://www.malicious.com"; + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel1(redirectUrl); + assertThat( + responseEntity + .getHeaders() + .get(LOCATION_HEADER_KEY) + .contains("https://www.malicious.com")) + .isTrue(); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.FOUND); + } + + @Test + @DisplayName( + "Level 2- test that the returnTo query parameter's value is not added to the Location header when it starts with http") + void + test_That_ReturnToQueryParameterValue_IsNotAddedToLocationHeader_WhenItStartsWith_Http_Level2() + throws URISyntaxException, MalformedURLException { + + String redirectUrl = "http://www.malicious.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI("https://somedomain.com?returnTo=http://www.malicious.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel2( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).isEqualTo(null); + } + + @Test + @DisplayName( + "Level 2- test that the returnTo query parameter's value is not added to the Location header when it starts with https") + void + test_That_ReturnToQueryParameterValue_IsNotAddedToLocationHeader_WhenItStartsWith_Https_Level2() + throws MalformedURLException, URISyntaxException { + String redirectUrl = "https://www.malicious.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI("https://somedomain.com?returnTo=https://www.malicious.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel2( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).isEqualTo(null); + } + + @Test + @DisplayName( + "Level 2- test that the returnTo query parameter's value is not added to the Location header when it starts with www") + void test_That_ReturnToQueryParameterValue_IsNotAddedToLocationHeader_WhenItStarts_WWW_Level2() + throws URISyntaxException, MalformedURLException { + + String redirectUrl = "www.malicious.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI("https://somedomain.com?returnTo=www.malicious.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel2( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).isEqualTo(null); + } + + @Test + @DisplayName( + "Level 2- test that the returnTo query parameter's value is directly added to the Location header when it does not start with http, https or www") + void + test_That_ReturnToQueryParameterValue_IsAddedToLocationHeader_WhenItDoesNotStartWith_Http_Https_Or_WWW_Level2() + throws URISyntaxException, MalformedURLException { + String redirectUrl = "ftp://ftp.dlptest.com/"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI("https://somedomain.com:8080?returnTo=ftp://ftp.dlptest.com/")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel2( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.FOUND); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)) + .contains("ftp://ftp.dlptest.com/"); + } + + @Test + @DisplayName( + "Level 2- test that the returnTo query parameter's value is directly added to the Location header when it is the same as the application domain") + void + test_That_ReturnToQueryParameterValue_IsAddedToLocationHeader_WhenItIsSameAs_ApplicationDomain_Level2() + throws MalformedURLException, URISyntaxException { + String redirectUrl = "somedomain.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, new URI("https://somedomain.com?returnTo=somedomain.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel2( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.FOUND); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).contains("somedomain.com"); + } + + @Test + @DisplayName( + "Level 3- test that the returnTo query parameter's value is not added to the Location header when it starts with http") + void + test_That_ReturnToQueryParameterValue_IsNotAddedToLocationHeader_WhenItStartsWith_Http_Level3() + throws MalformedURLException, URISyntaxException { + String redirectUrl = "http://www.malicious.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI("https://somedomain.com?returnTo=http://www.malicious.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel3( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).isEqualTo(null); + } + + @Test + @DisplayName( + "Level 3- test that the returnTo query parameter's value is not added to the Location header when it starts with https") + void + test_That_ReturnToQueryParameterValue_IsNotAddedToLocationHeader_WhenItStartsWith_Https_Level3() + throws MalformedURLException, URISyntaxException { + String redirectUrl = "https://www.malicious.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI("https://somedomain.com?returnTo=https://www.malicious.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel3( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).isEqualTo(null); + } + + @Test + @DisplayName( + "Level 3- test that the returnTo query parameter's value is not added to the Location header when it starts with double slashes") + void + test_That_ReturnToQueryParameterValue_IsNotAddedToLocationHeader_WhenItStartsWith_DoubleSlashes_Level3() + throws MalformedURLException, URISyntaxException { + String redirectUrl = "//www.malicious.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI("https://somedomain.com?returnTo=//www.malicious.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel3( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).isEqualTo(null); + } + + @Test + @DisplayName( + "Level 3- test that the returnTo query parameter's value is not added to the Location header when it starts with www") + void + test_That_ReturnToQueryParameterValue_IsNotAddedToLocationHeader_WhenItStartsWith_WWW_Level3() + throws MalformedURLException, URISyntaxException { + String redirectUrl = "www.malicious.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI("https://somedomain.com?returnTo=www.malicious.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel3( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).isEqualTo(null); + } + + @Test + @DisplayName( + "Level 3- test that the returnTo query parameter's value is directly added to the Location header when it does not start with http, https, // or www") + void + test_That_ReturnToQueryParameterValue_IsAddedToLocationHeader_WhenItDoesNotStartWith_Http_Https_DoubleSlashes_Or_WWW_Level3() + throws MalformedURLException, URISyntaxException { + String redirectUrl = "/%09/localdomain.pw"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI("https://somedomain.com:8080?returnTo=/%09/localdomain.pw")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel3( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.FOUND); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)) + .contains("/%09/localdomain.pw"); + } + + @Test + @DisplayName( + "Level 3- test that the returnTo query parameter's value is directly added to the Location header when it is the same as the application domain") + void + test_That_ReturnToQueryParameterValue_IsAddedToLocationHeader_WhenItIsSameAs_ApplicationDomain_Level3() + throws MalformedURLException, URISyntaxException { + String redirectUrl = "somedomain.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, new URI("https://somedomain.com?returnTo=somedomain.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel3( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.FOUND); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).contains("somedomain.com"); + } + + @Test + @DisplayName( + "Level 4- test that the returnTo query parameter's value is not added to the Location header when it starts with http") + void + test_That_ReturnToQueryParameterValue_IsNotAddedToLocationHeader_WhenItStartsWith_Http_Level4() + throws MalformedURLException, URISyntaxException { + String redirectUrl = "http://www.malicious.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI("https://somedomain.com?returnTo=http://www.malicious.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel4( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).isEqualTo(null); + } + + @Test + @DisplayName( + "Level 4- test that the returnTo query parameter's value is not added to the Location header when it starts with https") + void + test_That_ReturnToQueryParameterValue_IsNotAddedToLocationHeader_WhenItStartsWith_Https_Level4() + throws URISyntaxException, MalformedURLException { + + String redirectUrl = "https://www.malicious.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI("https://somedomain.com?returnTo=https://www.malicious.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel4( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).isEqualTo(null); + } + + @Test + @DisplayName( + "Level 4- test that the returnTo query parameter's value is not added to the Location header when it starts with double slashes") + void + test_That_ReturnToQueryParameterValue_IsNotAddedToLocationHeader_WhenItStartsWith_DoubleSlashes_Level4() + throws URISyntaxException, MalformedURLException { + + String redirectUrl = "//www.malicious.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI("https://somedomain.com?returnTo=//www.malicious.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel4( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).isEqualTo(null); + } + + @Test + @DisplayName( + "Level 4- test that the returnTo query parameter's value is not added to the Location header when it starts with www") + void + test_That_ReturnToQueryParameterValue_IsNotAddedToLocationHeader_WhenItStartsWith_WWW_Level4() + throws MalformedURLException, URISyntaxException { + String redirectUrl = "www.malicious.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI("https://somedomain.com?returnTo=www.malicious.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel4( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).isEqualTo(null); + } + + @Test + // test is disabled because building a RequestEntity with a NULL_BYTE_CHARACTER results in a + // URISyntaxException + @Disabled + @DisplayName( + "Level 4- test that the returnTo query parameter's value is not added to the Location header when it starts with a null byte character") + void + test_That_ReturnToQueryParameterValue_IsNotAddedToLocationHeader_WhenItStartsWith_Null_Byte_Character_Level4() + throws MalformedURLException, URISyntaxException { + String redirectUrl = Constants.NULL_BYTE_CHARACTER + "/localdomain.pw"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI( + "https://somedomain.com?returnTo=" + + Constants.NULL_BYTE_CHARACTER + + "/localdomain.pw")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel4( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).isEqualTo(null); + } + + @Test + @DisplayName( + "Level 4- test that the returnTo query parameter's value is directly added to the Location header when it does not start with http, https, //, null byte character or www") + void + test_That_ReturnToQueryParameterValue_IsAddedToLocationHeader_WhenItDoesNotStartWith_Http_Https_DoubleSlashes_Null_Byte_Character_Or_WWW_Level4() + throws MalformedURLException, URISyntaxException { + String redirectUrl = "/%09/localdomain.pw"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI("https://somedomain.com:8080?returnTo=/%09/localdomain.pw")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel4( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.FOUND); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)) + .contains("/%09/localdomain.pw"); + } + + @Test + @DisplayName( + "Level 4- test that the returnTo query parameter's value is directly added to the Location header when it is the same as the application domain") + void + test_That_ReturnToQueryParameterValue_IsAddedToLocationHeader_WhenItIsSameAs_ApplicationDomain_Level4() + throws MalformedURLException, URISyntaxException { + String redirectUrl = "somedomain.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, new URI("https://somedomain.com?returnTo=somedomain.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel4( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.FOUND); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).contains("somedomain.com"); + } + + @Test + @DisplayName( + "Level 5- test that the returnTo query parameter's value is not added to the Location header when it starts with Https") + void + test_That_ReturnToQueryParameterValue_IsNotAddedToLocationHeader_WhenItStartsWith_Https_Level5() + throws MalformedURLException, URISyntaxException { + String redirectUrl = "https://www.somedomain.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI("https://somedomain.com?returnTo=https://www.somedomain.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel5( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).isEqualTo(null); + } + + @Test + @DisplayName( + "Level 5- test that the returnTo query parameter's value is not added to the Location header when it starts with Http") + void + test_That_ReturnToQueryParameterValue_IsNotAddedToLocationHeader_WhenItStartsWith_Http_Level5() + throws MalformedURLException, URISyntaxException { + String redirectUrl = "http://www.somedomain.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI("https://somedomain.com?returnTo=http://www.somedomain.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel5( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).isEqualTo(null); + } + + @Test + @DisplayName( + "Level 5- test that the returnTo query parameter's value is not added to the Location header when it starts with www") + void + test_That_ReturnToQueryParameterValue_IsNotAddedToLocationHeader_WhenItStartsWith_WWW_Level5() + throws URISyntaxException, MalformedURLException { + String redirectUrl = "www.somedomain.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI("https://somedomain.com?returnTo=www.somedomain.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel5( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).isEqualTo(null); + } + + @Test + @DisplayName( + "Level 5- test that the returnTo query parameter's value is not added to the Location header when it starts with //") + void + test_That_ReturnToQueryParameterValue_IsNotAddedToLocationHeader_WhenItStartsWith_DoubleSlash_Level5() + throws MalformedURLException, URISyntaxException { + String redirectUrl = "//www.somedomain.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI("https://somedomain.com?returnTo=//www.somedomain.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel5( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).isEqualTo(null); + } + + @Test + // test is disabled because building a RequestEntity with a NULL_BYTE_CHARACTER results in a + // URISyntaxException + @Disabled + @DisplayName( + "Level 5- test that the returnTo query parameter's value is not added to the Location header when it starts with null byte character") + void + test_That_ReturnToQueryParameterValue_IsNotAddedToLocationHeader_WhenItStartsWith_NullByteCharacter_Level5() + throws MalformedURLException, URISyntaxException { + String redirectUrl = Constants.NULL_BYTE_CHARACTER + "//www.somedomain.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI( + "https://somedomain.com?returnTo=//" + + Constants.NULL_BYTE_CHARACTER + + "www.somedomain.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel5( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).isEqualTo(null); + } + + @Test + @DisplayName( + "Level 5- test that the returnTo query parameter's value is not added to the Location header when it is an empty string") + void + test_That_ReturnToQueryParameterValue_IsNotAddedToLocationHeader_WhenItIsAn_EmptyString_Level5() + throws URISyntaxException, MalformedURLException { + String redirectUrl = ""; + RequestEntity requestEntity = + new RequestEntity<>(HttpMethod.GET, new URI("https://somedomain.com?returnTo=")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel5( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).isEqualTo(null); + } + + @Test + @DisplayName( + "Level 5- test that the returnTo query parameter's value is added to the Location header when it does not start with https, http, www, //, or null byte character") + void + test_That_ReturnToQueryParameterValue_IsAddedToLocationHeader_WhenItDoesNotStartWith_Http_Https_DoubleSlashes_Null_Byte_Character_Or_WWW_Level5() + throws MalformedURLException, URISyntaxException { + String redirectUrl = "localdomain.pw/"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, + new URI("https://somedomain.com:8080?returnTo=localdomain.pw/")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel5( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.FOUND); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)) + .contains("localdomain.pw/"); + } + + @Test + @DisplayName( + "Level 5- test that the returnTo query parameter's value is directly added to the Location header when it is the same as the application domain") + void + test_That_ReturnToQueryParameterValue_IsAddedToLocationHeader_WhenItIsSameAs_ApplicationDomain_Level5() + throws MalformedURLException, URISyntaxException { + String redirectUrl = "somedomain.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, new URI("https://somedomain.com?returnTo=somedomain.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel5( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.FOUND); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).contains("somedomain.com"); + } + + @Test + @DisplayName( + "Level 6- test that the returnTo query parameter's value is directly added to the Location header by adding domain as prefix") + void + test_That_ReturnToQueryParameterValue_IsAddedToLocationHeader_ByAddingDomainToPrefix_Level6() + throws MalformedURLException, URISyntaxException { + String redirectUrl = "somedomain.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, new URI("https://somedomain.com?returnTo=somedomain.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel6( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.FOUND); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)) + .contains("https://somedomain.comsomedomain.com"); + } + + @Test + @DisplayName( + "Level 7- test that the returnTo query parameter's value is directly added to the Location header by adding domain as prefix") + void + test_That_ReturnToQueryParameterValue_IsAddedToLocationHeader_ByAddingDomainToPrefix_Level7() + throws MalformedURLException, URISyntaxException { + String redirectUrl = "/somedomain.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, new URI("https://somedomain.com?returnTo=/somedomain.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel7( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.FOUND); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)) + .contains("https://somedomain.com/somedomain.com"); + } + + @Test + @DisplayName( + "Level 8- test that the returnTo query parameter's value is not added to the Location header when it is not in the list of whitelisted URLS") + void + test_That_ReturnToQueryParameterValue_IsNotAddedToLocationHeader_WhenItIsNotInTheListOfWhiteListedURLS_Level8() + throws URISyntaxException { + // /somedomain.com is not in the list of whitelisted urls + String redirectUrl = "/somedomain.com"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, new URI("https://somedomain.com?returnTo=/somedomain.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel8( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).isEqualTo(null); + } + + @Test + @DisplayName( + "Level 8- test that the returnTo query parameter's value is not added to the Location header when it is not in the list of whitelisted URLS") + void + test_That_ReturnToQueryParameterValue_IsAddedToLocationHeader_WhenItIsInTheListOfWhiteListedURLS_Level8() + throws URISyntaxException { + String redirectUrl = "/"; + RequestEntity requestEntity = + new RequestEntity<>( + HttpMethod.GET, new URI("https://somedomain.com?returnTo=/somedomain.com")); + ResponseEntity responseEntity = + http3xxStatusCodeBasedInjection.getVulnerablePayloadLevel8( + requestEntity, redirectUrl); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.FOUND); + assertThat(responseEntity.getHeaders().get(LOCATION_HEADER_KEY)).contains("/"); + } +}