Skip to main content

Insecurely generated token - JWT

Need

Secure token generation for user creation service

Context

  • Usage of Dart 2.0 for developing high-performance, cross-platform applications
  • Usage of jaguar_jwt for JSON Web Token (JWT) authentication and authorization

Description

Non compliant code

import 'package:jaguar_jwt/jaguar_jwt.dart';

void main() {
final claimSet = JwtClaim(
issuer: 'DartAPI',
subject: 'userID',
expiry: DateTime.now().add(const Duration(minutes: 5)),
);

String token = issueJwtHS256(claimSet, 'weak_key');
print('Token: $token');
}

String issueJwtHS256(JwtClaim claimSet, String secret) {
final jwt = JwtClaim(
issuer: claimSet.issuer,
subject: claimSet.subject,
expiry: claimSet.expiry,
);

return 'Bearer ${jwt.compact(secret)}';
}

This Dart code is a simple example of a JWT token generation using the jaguar_jwt package. The main function creates a JwtClaim object with some claims such as issuer, subject, and expiry. Then, it generates a token by calling the issueJwtHS256 function with the claim set and a secret key.

The issueJwtHS256 function creates another JwtClaim object with the same claims as the input claim set. It then returns the compact form of the JWT, which is a string that includes the header, payload, and signature.

The vulnerability lies in the use of a weak secret key, 'weak_key', for signing the token. This key is easy to guess or crack, which means an attacker could potentially forge a token with any claims they want, including changing the expiry date to create a token that never expires. This would allow them to consume the user creation service indefinitely, and potentially perform other unauthorized actions in the application.

To mitigate this vulnerability, it is recommended to use a stronger, more complex secret key that is not easily guessable or crackable. This could be achieved, for example, by generating a random key of sufficient length and complexity, and storing it securely. Additionally, it may be beneficial to implement other security measures, such as token invalidation after a certain period of time or after certain events (e.g., user logout), to further reduce the risk of token misuse.

Steps

  • Replace the weak key with a strong, randomly generated key. The key should be long and complex enough to resist brute-force attacks.
  • Store the key securely. It should not be hard-coded into the application, but instead stored in a secure and encrypted configuration file or a secure key management system.
  • Implement a key rotation policy. Regularly changing the key can limit the damage if an old key is compromised.
  • Consider using asymmetric encryption for the JWT. This way, even if an attacker gets the public key, they cannot generate valid tokens without the private key.
  • Validate the token's expiration date on the server side. Even if an attacker modifies the token's expiration date, the server should reject tokens that have expired according to its own clock.

Compliant code

import 'dart:convert';
import 'dart:math';
import 'package:jaguar_jwt/jaguar_jwt.dart';
import 'package:crypto/crypto.dart';

void main() {
final claimSet = JwtClaim(
issuer: 'DartAPI',
subject: 'userID',
expiry: DateTime.now().add(const Duration(minutes: 5)),
);

String secret = generateSecretKey();
String token = issueJwtHS256(claimSet, secret);
print('Token: $token');
}

String generateSecretKey() {
var random = Random.secure();
var values = List<int>.generate(32, (i) => random.nextInt(256));
return base64Url.encode(values);
}

String issueJwtHS256(JwtClaim claimSet, String secret) {
final jwt = JwtClaim(
issuer: claimSet.issuer,
subject: claimSet.subject,
expiry: claimSet.expiry,
);

return 'Bearer ${jwt.compact(secret)}';
}

The updated code now includes a function generateSecretKey() that generates a strong, random secret key using Dart's Random.secure() function. This key is then used to sign the JWT token.

The generateSecretKey() function generates a list of 32 random integers (each between 0 and 255), and then encodes this list as a base64 URL string. This results in a 44-character long string that is suitable for use as a secret key for JWT.

Please note that this is a basic implementation and in a real-world scenario, the secret key should not be generated each time a token is issued. Instead, it should be generated once and stored securely, for example in an encrypted configuration file or a secure key management system.

Also, consider implementing a key rotation policy, where the secret key is changed regularly. This can limit the damage if an old key is compromised.

Finally, remember to validate the token's expiration date on the server side. Even if an attacker modifies the token's expiration date, the server should reject tokens that have expired according to its own clock.

References