Add extra documentation for using the correct algorithm but removing the signature.

This commit is contained in:
Nanne Baars 2021-09-29 13:42:52 +02:00 committed by Nanne Baars
parent ef4b7ce1a7
commit 14a6efedf3
6 changed files with 114 additions and 3 deletions

View File

@ -210,7 +210,7 @@ public class JWTLessonTest extends IntegrationTest {
private void quiz() {
Map<String, Object> 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);
}

View File

@ -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")

View File

@ -132,6 +132,18 @@
</div>
</div>
<div class="lesson-page-wrapper">
<div class="lesson-page-solution">
<div class="adoc-content" th:replace="doc:JWT_libraries_assignment2.adoc"></div>
</div>
</div>
<div class="lesson-page-wrapper">
<div class="lesson-page-solution">
<div class="adoc-content" th:replace="doc:JWT_libraries_solution.adoc"></div>
</div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:JWT_weak_keys"></div>
<script th:src="@{/lesson_js/jwt-weak-keys.js}" language="JavaScript"></script>

View File

@ -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 {

View File

@ -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);
}
----

View File

@ -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%]