feature: Add extra feedback once someone solves JWT refresh lesson differently
One can solve this lesson by using `alg:none` instead of using the refresh token flow. Instead of adding a check to force using the refresh token we opt for giving the user extra feedback.
This commit is contained in:
parent
73b8c431fc
commit
ecfc321f14
@ -22,7 +22,12 @@
|
|||||||
|
|
||||||
package org.owasp.webgoat.lessons.jwt;
|
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 io.jsonwebtoken.impl.TextCodec;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
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.AssignmentEndpoint;
|
||||||
import org.owasp.webgoat.container.assignments.AssignmentHints;
|
import org.owasp.webgoat.container.assignments.AssignmentHints;
|
||||||
import org.owasp.webgoat.container.assignments.AttackResult;
|
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;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* {
|
|
||||||
* "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"
|
|
||||||
* ]
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @author nbaars
|
|
||||||
* @since 4/23/17.
|
|
||||||
*/
|
|
||||||
@RestController
|
@RestController
|
||||||
@AssignmentHints({
|
@AssignmentHints({
|
||||||
"jwt-final-hint1",
|
"jwt-final-hint1",
|
||||||
|
@ -49,10 +49,6 @@ import org.springframework.web.bind.annotation.RequestHeader;
|
|||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
/**
|
|
||||||
* @author nbaars
|
|
||||||
* @since 4/23/17.
|
|
||||||
*/
|
|
||||||
@RestController
|
@RestController
|
||||||
@AssignmentHints({
|
@AssignmentHints({
|
||||||
"jwt-refresh-hint1",
|
"jwt-refresh-hint1",
|
||||||
@ -85,9 +81,7 @@ public class JWTRefreshEndpoint extends AssignmentEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> createNewTokens(String user) {
|
private Map<String, Object> createNewTokens(String user) {
|
||||||
Map<String, Object> claims = new HashMap<>();
|
Map<String, Object> claims = Map.of("admin", "false", "user", user);
|
||||||
claims.put("admin", "false");
|
|
||||||
claims.put("user", user);
|
|
||||||
String token =
|
String token =
|
||||||
Jwts.builder()
|
Jwts.builder()
|
||||||
.setIssuedAt(new Date(System.currentTimeMillis() + TimeUnit.DAYS.toDays(10)))
|
.setIssuedAt(new Date(System.currentTimeMillis() + TimeUnit.DAYS.toDays(10)))
|
||||||
@ -114,6 +108,9 @@ public class JWTRefreshEndpoint extends AssignmentEndpoint {
|
|||||||
Claims claims = (Claims) jwt.getBody();
|
Claims claims = (Claims) jwt.getBody();
|
||||||
String user = (String) claims.get("user");
|
String user = (String) claims.get("user");
|
||||||
if ("Tom".equals(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(success(this).build());
|
||||||
}
|
}
|
||||||
return ok(failed(this).feedback("jwt-refresh-not-tom").feedbackArgs(user).build());
|
return ok(failed(this).feedback("jwt-refresh-not-tom").feedbackArgs(user).build());
|
||||||
|
@ -42,10 +42,6 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
/**
|
|
||||||
* @author nbaars
|
|
||||||
* @since 4/23/17.
|
|
||||||
*/
|
|
||||||
@RestController
|
@RestController
|
||||||
@AssignmentHints({"jwt-secret-hint1", "jwt-secret-hint2", "jwt-secret-hint3"})
|
@AssignmentHints({"jwt-secret-hint1", "jwt-secret-hint2", "jwt-secret-hint3"})
|
||||||
public class JWTSecretKeyEndpoint extends AssignmentEndpoint {
|
public class JWTSecretKeyEndpoint extends AssignmentEndpoint {
|
||||||
|
@ -58,10 +58,6 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
|||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
/**
|
|
||||||
* @author nbaars
|
|
||||||
* @since 4/23/17.
|
|
||||||
*/
|
|
||||||
@RestController
|
@RestController
|
||||||
@AssignmentHints({
|
@AssignmentHints({
|
||||||
"jwt-change-token-hint1",
|
"jwt-change-token-hint1",
|
||||||
|
@ -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-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-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-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-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
|
jwt-final-not-tom=Username is not Tom try to pass a token for Tom
|
||||||
|
@ -29,6 +29,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.hamcrest.CoreMatchers;
|
import org.hamcrest.CoreMatchers;
|
||||||
@ -43,14 +44,14 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
|||||||
public class JWTRefreshEndpointTest extends LessonTest {
|
public class JWTRefreshEndpointTest extends LessonTest {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setup() {
|
void setup() {
|
||||||
when(webSession.getCurrentLesson()).thenReturn(new JWT());
|
when(webSession.getCurrentLesson()).thenReturn(new JWT());
|
||||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
|
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
|
||||||
when(webSession.getUserName()).thenReturn("unit-test");
|
when(webSession.getUserName()).thenReturn("unit-test");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void solveAssignment() throws Exception {
|
void solveAssignment() throws Exception {
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
// First login to obtain tokens for Jerry
|
// First login to obtain tokens for Jerry
|
||||||
@ -96,7 +97,26 @@ public class JWTRefreshEndpointTest extends LessonTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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 =
|
String accessTokenTom =
|
||||||
"eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q";
|
"eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q";
|
||||||
mockMvc
|
mockMvc
|
||||||
@ -108,7 +128,7 @@ public class JWTRefreshEndpointTest extends LessonTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkoutWitRandomTokenShouldFail() throws Exception {
|
void checkoutWitRandomTokenShouldFail() throws Exception {
|
||||||
String accessTokenTom =
|
String accessTokenTom =
|
||||||
"eyJhbGciOiJIUzUxMiJ9.eyJpLXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q";
|
"eyJhbGciOiJIUzUxMiJ9.eyJpLXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q";
|
||||||
mockMvc
|
mockMvc
|
||||||
@ -121,7 +141,7 @@ public class JWTRefreshEndpointTest extends LessonTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void flowForJerryAlwaysWorks() throws Exception {
|
void flowForJerryAlwaysWorks() throws Exception {
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
var loginJson = Map.of("user", "Jerry", "password", PASSWORD);
|
var loginJson = Map.of("user", "Jerry", "password", PASSWORD);
|
||||||
@ -146,7 +166,7 @@ public class JWTRefreshEndpointTest extends LessonTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void loginShouldNotWorkForJerryWithWrongPassword() throws Exception {
|
void loginShouldNotWorkForJerryWithWrongPassword() throws Exception {
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
var loginJson = Map.of("user", "Jerry", "password", PASSWORD + "wrong");
|
var loginJson = Map.of("user", "Jerry", "password", PASSWORD + "wrong");
|
||||||
@ -159,7 +179,7 @@ public class JWTRefreshEndpointTest extends LessonTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void loginShouldNotWorkForTom() throws Exception {
|
void loginShouldNotWorkForTom() throws Exception {
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
var loginJson = Map.of("user", "Tom", "password", PASSWORD);
|
var loginJson = Map.of("user", "Tom", "password", PASSWORD);
|
||||||
@ -172,7 +192,7 @@ public class JWTRefreshEndpointTest extends LessonTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void newTokenShouldWorkForJerry() throws Exception {
|
void newTokenShouldWorkForJerry() throws Exception {
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
Map<String, Object> loginJson = new HashMap<>();
|
Map<String, Object> loginJson = new HashMap<>();
|
||||||
loginJson.put("user", "Jerry");
|
loginJson.put("user", "Jerry");
|
||||||
@ -201,7 +221,7 @@ public class JWTRefreshEndpointTest extends LessonTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void unknownRefreshTokenShouldGiveUnauthorized() throws Exception {
|
void unknownRefreshTokenShouldGiveUnauthorized() throws Exception {
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
Map<String, Object> loginJson = new HashMap<>();
|
Map<String, Object> loginJson = new HashMap<>();
|
||||||
loginJson.put("user", "Jerry");
|
loginJson.put("user", "Jerry");
|
||||||
@ -229,21 +249,21 @@ public class JWTRefreshEndpointTest extends LessonTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void noTokenWhileCheckoutShouldReturn401() throws Exception {
|
void noTokenWhileCheckoutShouldReturn401() throws Exception {
|
||||||
mockMvc
|
mockMvc
|
||||||
.perform(MockMvcRequestBuilders.post("/JWT/refresh/checkout"))
|
.perform(MockMvcRequestBuilders.post("/JWT/refresh/checkout"))
|
||||||
.andExpect(status().isUnauthorized());
|
.andExpect(status().isUnauthorized());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void noTokenWhileRequestingNewTokenShouldReturn401() throws Exception {
|
void noTokenWhileRequestingNewTokenShouldReturn401() throws Exception {
|
||||||
mockMvc
|
mockMvc
|
||||||
.perform(MockMvcRequestBuilders.post("/JWT/refresh/newToken"))
|
.perform(MockMvcRequestBuilders.post("/JWT/refresh/newToken"))
|
||||||
.andExpect(status().isUnauthorized());
|
.andExpect(status().isUnauthorized());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void noTokenWhileLoginShouldReturn401() throws Exception {
|
void noTokenWhileLoginShouldReturn401() throws Exception {
|
||||||
mockMvc
|
mockMvc
|
||||||
.perform(MockMvcRequestBuilders.post("/JWT/refresh/login"))
|
.perform(MockMvcRequestBuilders.post("/JWT/refresh/login"))
|
||||||
.andExpect(status().isUnauthorized());
|
.andExpect(status().isUnauthorized());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user