Skip to main content

Business information leak - JWT

Need

Protection of sensitive business information within JWT

Context

  • Usage of C# 7.0 for modern language features and enhancements
  • Usage of Microsoft.AspNetCore.Mvc for building web applications using the MVC pattern
  • Usage of System.IdentityModel.Tokens.Jwt for JWT (JSON Web Token) authentication and authorization
  • Usage of System.Text.Encoding for character encoding and decoding operations
  • Usage of System.IdentityModel.Tokens for handling authentication and authorization tokens
  • Usage of System.Security.Claims for managing and working with claims-based authentication and authorization in a system.
  • Usage of System.IdentityModel.Tokens.Jwt for JWT (JSON Web Token) authentication and authorization
  • Usage of Microsoft.IdentityModel.Tokens for token-based authentication and authorization

Description

Non compliant code

public class AccountController : Controller
{
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]User userParam)
{
var user = Authenticate(userParam.Username, userParam.Password);

if (user == null)
return BadRequest(new { message = "Username or password is incorrect" });

var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("your_secret_key_here");
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.Username.ToString()),
new Claim(ClaimTypes.NameIdentifier, user.Password.ToString())
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);

return Ok(new {
Id = user.Id,
Username = user.Username,
Token = tokenString
});
}
}

This code represents an ASP.NET Core controller action that authenticates a user and generates a JWT (JSON Web Token) containing sensitive user information, such as the username and password.

The vulnerability lies in the SecurityTokenDescriptor object creation, where the ClaimsIdentity is populated with the user's username and password. This is a critical security issue because JWTs are not encrypted but encoded, which means that the information within the token can be easily decoded and read by anyone who gets access to the token.

The JwtSecurityTokenHandler object creates a JWT based on the SecurityTokenDescriptor and writes it into a string. This token string, containing the sensitive user information, is then sent back to the client as part of the response.

This exposes the user's password in plain text, which is a significant security risk. If an attacker intercepts this token, they will be able to decode it and gain unauthorized access to the user's account.

Steps

  • Do not include sensitive information such as username and password in the JWT payload.
  • Instead, store only the user's identifier or any non-sensitive information in the JWT payload.
  • When generating the JWT, use a secure random key or a strong cryptographic algorithm to sign the token.
  • Do not hardcode the secret key in the code. Store it securely in a configuration file or a secret management system.
  • Consider using a library or framework that provides built-in JWT authentication and authorization mechanisms.
  • Validate the JWT signature and claims on the server-side to ensure the token is not tampered with.
  • Implement proper authentication and authorization mechanisms to protect sensitive business information.
  • Regularly review and update the code to address any new vulnerabilities or security best practices.

Compliant code

public class AccountController : Controller
{
private readonly IConfiguration _config;

public AccountController(IConfiguration config)
{
_config = config;
}

[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]User userParam)
{
var user = Authenticate(userParam.Username, userParam.Password);

if (user == null)
return BadRequest(new { message = "Username or password is incorrect" });

var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_config["Jwt:Key"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);

return Ok(new {
Id = user.Id,
Username = user.Username,
Token = tokenString
});
}
}

The updated code now securely handles JWTs by not including sensitive information such as the username and password in the JWT payload. Instead, it only includes the user's identifier (Id) which is a non-sensitive piece of information.

The secret key used for signing the JWT is no longer hardcoded in the code. It is now securely stored in a configuration file and accessed through the IConfiguration interface. This prevents the secret key from being exposed in the code.

The JWT is signed using a secure cryptographic algorithm (HmacSha256Signature). This ensures the integrity of the JWT and prevents it from being tampered with.

The Authenticate method validates the user's credentials and generates a JWT if the credentials are valid. The JWT is then returned in the response. This JWT must be included in the Authorization header of subsequent requests to authenticate the user.

The server-side code validates the JWT signature and claims to ensure the token is not tampered with. This provides an additional layer of security and protects sensitive business information.

By implementing these changes, the code now follows security best practices for handling JWTs and protects against business information leaks.

References