Skip to main content

Insecurely generated token - Validation

Need

Secure token generation and validation

Context

  • Usage of Dart 2.0 for developing high-performance, cross-platform applications
  • Usage of Shelf for managing and organizing data in a Node.js application
  • Usage of shelf_io for managing input/output operations with Shelf
  • Usage of dart:convert for encoding and decoding JSON data in Dart
  • Usage of dart:io for performing input/output operations in Dart
  • Usage of jaguar_jwt for JSON Web Token (JWT) authentication and authorization

Description

Non compliant code

import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as io;
import 'dart:convert';
import 'dart:io';
import 'package:jaguar_jwt/jaguar_jwt.dart';

void main() {
var handler = const shelf.Pipeline().addMiddleware(shelf.logRequests()).addHandler(_echoRequest);

io.serve(handler, 'localhost', 8080);
}

shelf.Response _echoRequest(shelf.Request request) {
var token = request.headers['authorization'];

if (token == null) {
return new shelf.Response(401);
}

var decodedToken = B64urlEncRfc7515.decodeUtf8(token);

var jsonToken = jsonDecode(decodedToken);

var userId = jsonToken['user_id'];

if (userId == null) {
return new shelf.Response(401);
}

return new shelf.Response.ok('Hello, $userId!');
}

The above code is a simple server application using the Shelf framework in Dart. It listens for requests on localhost at port 8080.

In the _echoRequest function, it tries to retrieve the 'authorization' header from the request which is supposed to contain a JWT token. If the token is not present, it responds with a 401 status code.

The vulnerability lies in the way it handles the token. It directly decodes the token using B64urlEncRfc7515.decodeUtf8(token). This means that it does not validate the signature of the token. Hence, an attacker can modify the token, change the algorithm in the header to 'none', and the server would still accept it as a valid token.

Moreover, it retrieves the 'user_id' from the decoded token and if it is present, it sends an OK response with a message 'Hello, user_id!'. This means an attacker can impersonate any user by simply creating a token with the user's id.

Steps

  • Introduce a secret key to sign the JWT token. This key should be stored securely and not exposed.
  • Use a library or function to validate the JWT token. This function should verify the signature of the token using the secret key.
  • Ensure that the algorithm used to sign the token is secure and not 'none'.
  • Check the claims of the JWT token. In this case, make sure that the 'user_id' claim is present and valid.
  • Handle the case where the token is invalid or tampered with by returning an appropriate error response.

Compliant code

import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as io;
import 'dart:convert';
import 'dart:io';
import 'package:jaguar_jwt/jaguar_jwt.dart';

const String secretKey = 'your-secure-secret-key'; // Store this securely

void main() {
var handler = const shelf.Pipeline().addMiddleware(shelf.logRequests()).addHandler(_echoRequest);

io.serve(handler, 'localhost', 8080);
}

shelf.Response _echoRequest(shelf.Request request) {
var token = request.headers['authorization'];

if (token == null) {
return new shelf.Response(401);
}

try {
var decClaimSet = verifyJwtHS256Signature(token, secretKey);
var userId = decClaimSet.uniqueId;

if (userId == null) {
return new shelf.Response(401);
}

return new shelf.Response.ok('Hello, $userId!');
} catch(e) {
return new shelf.Response(401);
}
}

The updated code introduces a secret key to sign the JWT token. This key is stored securely and not exposed. The verifyJwtHS256Signature function from the jaguar_jwt library is used to validate the JWT token. This function verifies the signature of the token using the secret key.

The algorithm used to sign the token is HS256, which is a secure algorithm. The 'none' algorithm is not used, preventing the vulnerability where the token could be unsigned.

The claims of the JWT token are checked by the verifyJwtHS256Signature function. In this case, it makes sure that the 'user_id' claim is present and valid. The 'user_id' claim is accessed through the uniqueId property of the decoded claim set.

If the token is invalid or tampered with, an exception is thrown and caught in the catch block. In this case, a 401 Unauthorized response is returned, appropriately handling the error case.

References