diff --git a/src/main/java/org/owasp/webgoat/lessons/jwt/JWTFinalEndpoint.java b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTFinalEndpoint.java index e84bbf809..dfebaa03d 100644 --- a/src/main/java/org/owasp/webgoat/lessons/jwt/JWTFinalEndpoint.java +++ b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTFinalEndpoint.java @@ -22,7 +22,12 @@ package org.owasp.webgoat.lessons.jwt; -import io.jsonwebtoken.*; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwsHeader; +import io.jsonwebtoken.Jwt; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SigningKeyResolverAdapter; import io.jsonwebtoken.impl.TextCodec; import java.sql.ResultSet; import java.sql.SQLException; @@ -31,34 +36,12 @@ import org.owasp.webgoat.container.LessonDataSource; import org.owasp.webgoat.container.assignments.AssignmentEndpoint; import org.owasp.webgoat.container.assignments.AssignmentHints; import org.owasp.webgoat.container.assignments.AttackResult; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; -/** - * - * - *
- *  {
- *      "typ": "JWT",
- *      "kid": "webgoat_key",
- *      "alg": "HS256"
- *  }
- *  {
- *       "iss": "WebGoat Token Builder",
- *       "iat": 1524210904,
- *       "exp": 1618905304,
- *       "aud": "webgoat.org",
- *       "sub": "jerry@webgoat.com",
- *       "username": "Jerry",
- *       "Email": "jerry@webgoat.com",
- *       "Role": [
- *       "Cat"
- *       ]
- *  }
- * 
- * - * @author nbaars - * @since 4/23/17. - */ @RestController @AssignmentHints({ "jwt-final-hint1", diff --git a/src/main/java/org/owasp/webgoat/lessons/jwt/JWTRefreshEndpoint.java b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTRefreshEndpoint.java index 945bb57da..4efc9db09 100644 --- a/src/main/java/org/owasp/webgoat/lessons/jwt/JWTRefreshEndpoint.java +++ b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTRefreshEndpoint.java @@ -49,10 +49,6 @@ import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; -/** - * @author nbaars - * @since 4/23/17. - */ @RestController @AssignmentHints({ "jwt-refresh-hint1", @@ -85,9 +81,7 @@ public class JWTRefreshEndpoint extends AssignmentEndpoint { } private Map createNewTokens(String user) { - Map claims = new HashMap<>(); - claims.put("admin", "false"); - claims.put("user", user); + Map claims = Map.of("admin", "false", "user", user); String token = Jwts.builder() .setIssuedAt(new Date(System.currentTimeMillis() + TimeUnit.DAYS.toDays(10))) @@ -114,6 +108,9 @@ public class JWTRefreshEndpoint extends AssignmentEndpoint { Claims claims = (Claims) jwt.getBody(); String user = (String) claims.get("user"); if ("Tom".equals(user)) { + if ("none".equals(jwt.getHeader().get("alg"))) { + return ok(success(this).feedback("jwt-refresh-alg-none").build()); + } return ok(success(this).build()); } return ok(failed(this).feedback("jwt-refresh-not-tom").feedbackArgs(user).build()); diff --git a/src/main/java/org/owasp/webgoat/lessons/jwt/JWTSecretKeyEndpoint.java b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTSecretKeyEndpoint.java index dac1ef5cc..0e688c049 100644 --- a/src/main/java/org/owasp/webgoat/lessons/jwt/JWTSecretKeyEndpoint.java +++ b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTSecretKeyEndpoint.java @@ -42,10 +42,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; -/** - * @author nbaars - * @since 4/23/17. - */ @RestController @AssignmentHints({"jwt-secret-hint1", "jwt-secret-hint2", "jwt-secret-hint3"}) public class JWTSecretKeyEndpoint extends AssignmentEndpoint { diff --git a/src/main/java/org/owasp/webgoat/lessons/jwt/JWTVotesEndpoint.java b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTVotesEndpoint.java index 632449822..02a935498 100644 --- a/src/main/java/org/owasp/webgoat/lessons/jwt/JWTVotesEndpoint.java +++ b/src/main/java/org/owasp/webgoat/lessons/jwt/JWTVotesEndpoint.java @@ -58,10 +58,6 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -/** - * @author nbaars - * @since 4/23/17. - */ @RestController @AssignmentHints({ "jwt-change-token-hint1", diff --git a/src/main/resources/lessons/jwt/i18n/WebGoatLabels.properties b/src/main/resources/lessons/jwt/i18n/WebGoatLabels.properties index 70ac7a4a1..ed05f46b2 100644 --- a/src/main/resources/lessons/jwt/i18n/WebGoatLabels.properties +++ b/src/main/resources/lessons/jwt/i18n/WebGoatLabels.properties @@ -21,6 +21,7 @@ jwt-refresh-hint2=The token from the access log is no longer valid, can you find jwt-refresh-hint3=The endpoint for refreshing a token is 'JWT/refresh/newToken' jwt-refresh-hint4=Use the found access token in the Authorization: Bearer header and use your own refresh token jwt-refresh-not-tom=User is not Tom but {0}, please try again +jwt-refresh-alg-none=Nicely found! You solved the assignment with 'alg: none' can you also solve it by using the refresh token? jwt-final-jerry-account=Yikes, you are removing Jerry's account, try to delete the account of Tom jwt-final-not-tom=Username is not Tom try to pass a token for Tom @@ -30,4 +31,4 @@ jwt-final-hint2=The 'kid' (key ID) header parameter is a hint indicating which k jwt-final-hint3=The key can be located on the filesystem in memory or even reside in the database jwt-final-hint4=The key is stored in the database and loaded while verifying a token jwt-final-hint5=Using a SQL injection you might be able to manipulate the key to something you know and create a new token. -jwt-final-hint6=Use: hacked' UNION select 'deletingTom' from INFORMATION_SCHEMA.SYSTEM_USERS -- as the kid in the header and change the contents of the token to Tom and hit the endpoint with the new token \ No newline at end of file +jwt-final-hint6=Use: hacked' UNION select 'deletingTom' from INFORMATION_SCHEMA.SYSTEM_USERS -- as the kid in the header and change the contents of the token to Tom and hit the endpoint with the new token diff --git a/src/test/java/org/owasp/webgoat/lessons/jwt/JWTRefreshEndpointTest.java b/src/test/java/org/owasp/webgoat/lessons/jwt/JWTRefreshEndpointTest.java index 36eb18305..028717706 100644 --- a/src/test/java/org/owasp/webgoat/lessons/jwt/JWTRefreshEndpointTest.java +++ b/src/test/java/org/owasp/webgoat/lessons/jwt/JWTRefreshEndpointTest.java @@ -29,6 +29,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; +import io.jsonwebtoken.Jwts; import java.util.HashMap; import java.util.Map; import org.hamcrest.CoreMatchers; @@ -43,14 +44,14 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; public class JWTRefreshEndpointTest extends LessonTest { @BeforeEach - public void setup() { + void setup() { when(webSession.getCurrentLesson()).thenReturn(new JWT()); this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); when(webSession.getUserName()).thenReturn("unit-test"); } @Test - public void solveAssignment() throws Exception { + void solveAssignment() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); // First login to obtain tokens for Jerry @@ -96,7 +97,26 @@ public class JWTRefreshEndpointTest extends LessonTest { } @Test - public void checkoutWithTomsTokenFromAccessLogShouldFail() throws Exception { + void solutionWithAlgNone() throws Exception { + String tokenWithNoneAlgorithm = + Jwts.builder() + .setHeaderParam("alg", "none") + .addClaims(Map.of("admin", "true", "user", "Tom")) + .compact(); + + // Now checkout with the new token from Tom + mockMvc + .perform( + MockMvcRequestBuilders.post("/JWT/refresh/checkout") + .header("Authorization", "Bearer " + tokenWithNoneAlgorithm)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.lessonCompleted", is(true))) + .andExpect( + jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("jwt-refresh-alg-none")))); + } + + @Test + void checkoutWithTomsTokenFromAccessLogShouldFail() throws Exception { String accessTokenTom = "eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q"; mockMvc @@ -108,7 +128,7 @@ public class JWTRefreshEndpointTest extends LessonTest { } @Test - public void checkoutWitRandomTokenShouldFail() throws Exception { + void checkoutWitRandomTokenShouldFail() throws Exception { String accessTokenTom = "eyJhbGciOiJIUzUxMiJ9.eyJpLXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q"; mockMvc @@ -121,7 +141,7 @@ public class JWTRefreshEndpointTest extends LessonTest { } @Test - public void flowForJerryAlwaysWorks() throws Exception { + void flowForJerryAlwaysWorks() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); var loginJson = Map.of("user", "Jerry", "password", PASSWORD); @@ -146,7 +166,7 @@ public class JWTRefreshEndpointTest extends LessonTest { } @Test - public void loginShouldNotWorkForJerryWithWrongPassword() throws Exception { + void loginShouldNotWorkForJerryWithWrongPassword() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); var loginJson = Map.of("user", "Jerry", "password", PASSWORD + "wrong"); @@ -159,7 +179,7 @@ public class JWTRefreshEndpointTest extends LessonTest { } @Test - public void loginShouldNotWorkForTom() throws Exception { + void loginShouldNotWorkForTom() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); var loginJson = Map.of("user", "Tom", "password", PASSWORD); @@ -172,7 +192,7 @@ public class JWTRefreshEndpointTest extends LessonTest { } @Test - public void newTokenShouldWorkForJerry() throws Exception { + void newTokenShouldWorkForJerry() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); Map loginJson = new HashMap<>(); loginJson.put("user", "Jerry"); @@ -201,7 +221,7 @@ public class JWTRefreshEndpointTest extends LessonTest { } @Test - public void unknownRefreshTokenShouldGiveUnauthorized() throws Exception { + void unknownRefreshTokenShouldGiveUnauthorized() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); Map loginJson = new HashMap<>(); loginJson.put("user", "Jerry"); @@ -229,21 +249,21 @@ public class JWTRefreshEndpointTest extends LessonTest { } @Test - public void noTokenWhileCheckoutShouldReturn401() throws Exception { + void noTokenWhileCheckoutShouldReturn401() throws Exception { mockMvc .perform(MockMvcRequestBuilders.post("/JWT/refresh/checkout")) .andExpect(status().isUnauthorized()); } @Test - public void noTokenWhileRequestingNewTokenShouldReturn401() throws Exception { + void noTokenWhileRequestingNewTokenShouldReturn401() throws Exception { mockMvc .perform(MockMvcRequestBuilders.post("/JWT/refresh/newToken")) .andExpect(status().isUnauthorized()); } @Test - public void noTokenWhileLoginShouldReturn401() throws Exception { + void noTokenWhileLoginShouldReturn401() throws Exception { mockMvc .perform(MockMvcRequestBuilders.post("/JWT/refresh/login")) .andExpect(status().isUnauthorized());