Add extra documentation for using the correct algorithm but removing the signature.
This commit is contained in:
parent
ef4b7ce1a7
commit
14a6efedf3
@ -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);
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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>
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
----
|
@ -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%]
|
Loading…
x
Reference in New Issue
Block a user