From 14a6efedf3e5e91fc67284c0e3790a92f2a98485 Mon Sep 17 00:00:00 2001 From: Nanne Baars Date: Wed, 29 Sep 2021 13:42:52 +0200 Subject: [PATCH] Add extra documentation for using the correct algorithm but removing the signature. --- .../java/org/owasp/webgoat/JWTLessonTest.java | 2 +- .../java/org/owasp/webgoat/jwt/JWTQuiz.java | 2 +- .../jwt/src/main/resources/html/JWT.html | 12 +++++ .../en/JWT_libraries_assignment.adoc | 18 +++++++- .../en/JWT_libraries_assignment2.adoc | 37 +++++++++++++++ .../en/JWT_libraries_solution.adoc | 46 +++++++++++++++++++ 6 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_libraries_assignment2.adoc create mode 100644 webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_libraries_solution.adoc diff --git a/webgoat-integration-tests/src/test/java/org/owasp/webgoat/JWTLessonTest.java b/webgoat-integration-tests/src/test/java/org/owasp/webgoat/JWTLessonTest.java index 4a6513440..8913e4351 100644 --- a/webgoat-integration-tests/src/test/java/org/owasp/webgoat/JWTLessonTest.java +++ b/webgoat-integration-tests/src/test/java/org/owasp/webgoat/JWTLessonTest.java @@ -210,7 +210,7 @@ public class JWTLessonTest extends IntegrationTest { private void quiz() { Map params = new HashMap<>(); params.put("question_0_solution", "Solution 1"); - params.put("question_1_solution", "Solution 3"); + params.put("question_1_solution", "Solution 2"); checkAssignment(url("/WebGoat/JWT/quiz"), params, true); } diff --git a/webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTQuiz.java b/webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTQuiz.java index 256e4be2e..0eebc255b 100644 --- a/webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTQuiz.java +++ b/webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTQuiz.java @@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController public class JWTQuiz extends AssignmentEndpoint { - private final String[] solutions = {"Solution 1", "Solution 3"}; + private final String[] solutions = {"Solution 1", "Solution 2"}; private final boolean[] guesses = new boolean[solutions.length]; @PostMapping("/JWT/quiz") diff --git a/webgoat-lessons/jwt/src/main/resources/html/JWT.html b/webgoat-lessons/jwt/src/main/resources/html/JWT.html index d5cb0aad8..20097ca15 100644 --- a/webgoat-lessons/jwt/src/main/resources/html/JWT.html +++ b/webgoat-lessons/jwt/src/main/resources/html/JWT.html @@ -132,6 +132,18 @@ +
+
+
+
+
+ +
+
+
+
+
+
diff --git a/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_libraries_assignment.adoc b/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_libraries_assignment.adoc index 1937f084e..33e6418f6 100644 --- a/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_libraries_assignment.adoc +++ b/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_libraries_assignment.adoc @@ -7,6 +7,22 @@ Now let's look at a code review and try to think on an attack with the `alg: non eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwidXNlciI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0. ---- +which after decoding becomes: + +[source] +---- +{ + "alg" : "none", + "typ" : "JWT" +}, +{ + "admin" : true, + "iat" : 1516239022, + "sub" : "1234567890", + "user" : "John Doe" +} +---- + [source%linenums, java] ---- try { @@ -30,7 +46,7 @@ try { Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken); Claims claims = (Claims) jwt.getBody(); String user = (String) claims.get("user"); - boolean isAdmin = Boolean.valueOf((String) claims.get("admin")); + booelean isAdmin = Boolean.valueOf((String) claims.get("admin")); if (isAdmin) { removeAllUsers(); } else { diff --git a/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_libraries_assignment2.adoc b/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_libraries_assignment2.adoc new file mode 100644 index 000000000..e1aa7c11c --- /dev/null +++ b/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_libraries_assignment2.adoc @@ -0,0 +1,37 @@ +== Code review (2) + +Same as before but now we are only removing the signature part, leaving the algorithm as is. + +[source] +---- +eyJhbGciOiJIUzI1NiJ9.ew0KICAiYWRtaW4iIDogdHJ1ZSwNCiAgImlhdCIgOiAxNTE2MjM5MDIyLA0KICAic3ViIiA6ICIxMjM0NTY3ODkwIiwNCiAgInVzZXIiIDogIkpvaG4gRG9lIg0KfQ. + +{ + "alg" : "HS256" +}, +{ + "admin" : true, + "iat" : 1516239022, + "sub" : "1234567890", + "user" : "John Doe" +} +---- + +Using the following `parse` method we are still able to skip the signature check. + +[source%linenums, java] +---- +try { + Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken); + Claims claims = (Claims) jwt.getBody(); + String user = (String) claims.get("user"); + boolean isAdmin = Boolean.valueOf((String) claims.get("admin")); + if (isAdmin) { + removeAllUsers(); + } else { + log.error("You are not an admin user"); + } +} catch (JwtException e) { + throw new InvalidTokenException(e); +} +---- diff --git a/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_libraries_solution.adoc b/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_libraries_solution.adoc new file mode 100644 index 000000000..f01c33afc --- /dev/null +++ b/webgoat-lessons/jwt/src/main/resources/lessonPlans/en/JWT_libraries_solution.adoc @@ -0,0 +1,46 @@ +=== Solution + +In the past assignments we learned to **NOT** trust the libraries to do the correct thing for us. In both cases we saw that even specifying the JWT key and passing the correct algorithm. Even using the token: + +[source] +---- +eyJhbGciOiJIUzI1NiJ9.ew0KICAiYWRtaW4iIDogdHJ1ZSwNCiAgImlhdCIgOiAxNTE2MjM5MDIyLA0KICAic3ViIiA6ICIxMjM0NTY3ODkwIiwNCiAgInVzZXIiIDogIkpvaG4gRG9lIg0KfQ. + +{ + "alg" : "HS256" +}, +{ + "admin" : true, + "iat" : 1516239022, + "sub" : "1234567890", + "user" : "John Doe" +} +---- + +And the following Java code: + +[source] +---- +Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken); +---- + +You see we set the signing key with `setSigningKey` the library still skips the validation of the signature. + +It is not only limited to the traditional `alg: none` attack, but it also works with the `alg: HS256`. + +=== Conclusion + +When you have chosen a library to help dealing with JWT tokens make sure to: + +- use the correct method in your code when validating tokens. +- add test cases and validate the algorithm confusion is not possible. +- as a security team write a utility methods to be used by the teams which encapsulate the library to make sure the teams use the correct parsing logic. + +=== Alternative: Paseto + +The algorithm confusion is a real problem when dealing with JWTs it can be avoided by using PASETO (**P**latform-**A**gnostic **SE**curity **TO**kens), which is currently implemented in 10 programming languages. +One of the drawbacks of using this method is that JWT is widely spread for example think about using OAuth, so it might not be the best solution to use. + +For more information take a look at the following video: + +video::RijGNytjbOI[youtube, height=480, width=100%] \ No newline at end of file