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() {
|
private void quiz() {
|
||||||
Map<String, Object> params = new HashMap<>();
|
Map<String, Object> params = new HashMap<>();
|
||||||
params.put("question_0_solution", "Solution 1");
|
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);
|
checkAssignment(url("/WebGoat/JWT/quiz"), params, true);
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
@RestController
|
@RestController
|
||||||
public class JWTQuiz extends AssignmentEndpoint {
|
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];
|
private final boolean[] guesses = new boolean[solutions.length];
|
||||||
|
|
||||||
@PostMapping("/JWT/quiz")
|
@PostMapping("/JWT/quiz")
|
||||||
|
@ -132,6 +132,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</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="lesson-page-wrapper">
|
||||||
<div class="adoc-content" th:replace="doc:JWT_weak_keys"></div>
|
<div class="adoc-content" th:replace="doc:JWT_weak_keys"></div>
|
||||||
<script th:src="@{/lesson_js/jwt-weak-keys.js}" language="JavaScript"></script>
|
<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.
|
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwidXNlciI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.
|
||||||
----
|
----
|
||||||
|
|
||||||
|
which after decoding becomes:
|
||||||
|
|
||||||
|
[source]
|
||||||
|
----
|
||||||
|
{
|
||||||
|
"alg" : "none",
|
||||||
|
"typ" : "JWT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"admin" : true,
|
||||||
|
"iat" : 1516239022,
|
||||||
|
"sub" : "1234567890",
|
||||||
|
"user" : "John Doe"
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
[source%linenums, java]
|
[source%linenums, java]
|
||||||
----
|
----
|
||||||
try {
|
try {
|
||||||
@ -30,7 +46,7 @@ try {
|
|||||||
Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken);
|
Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken);
|
||||||
Claims claims = (Claims) jwt.getBody();
|
Claims claims = (Claims) jwt.getBody();
|
||||||
String user = (String) claims.get("user");
|
String user = (String) claims.get("user");
|
||||||
boolean isAdmin = Boolean.valueOf((String) claims.get("admin"));
|
booelean isAdmin = Boolean.valueOf((String) claims.get("admin"));
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
removeAllUsers();
|
removeAllUsers();
|
||||||
} else {
|
} 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