Add extra documentation for using the correct algorithm but removing the signature.
This commit is contained in:
		| @ -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%] | ||||
		Reference in New Issue
	
	Block a user