Skip to main content

Business information leak - JWT

Need

Protection of sensitive business information in JWT

Context

  • Usage of Scala 2.11 for developing scalable and high-performance applications
  • Usage of play.api.mvc for handling HTTP requests and responses in Play Framework
  • Usage of pdi.jwt for JSON Web Token (JWT) authentication and authorization
  • Usage of spray.json for JSON parsing and serialization
  • Usage of spray.json.DefaultJsonProtocol for JSON serialization and deserialization

Description

Non compliant code

import play.api.mvc._
import pdi.jwt._

class UserController extends Controller {
def login = Action { request =>
val username = request.body.asFormUrlEncoded.get("username")(0)
val password = request.body.asFormUrlEncoded.get("password")(0)

// Sign the JWT with the username and password
val claim = Json.obj(("username", username), ("password", password))
val jwt = JwtSprayJson.encode(claim, "secretKey", JwtAlgorithm.HS256)

Ok.withCookies(Cookie("jwt", jwt))
}
}

In the above code, when a user logs in, the application takes the username and password from the request body and encodes them into a JWT (JSON Web Token). The JWT is then sent back to the client in a cookie.

The vulnerability lies in the fact that the JWT contains sensitive business information, in this case, the user's username and password. JWTs are not encrypted, but are merely base64-encoded. This means that anyone who intercepts the JWT can easily decode it and access the sensitive information it contains.

Furthermore, the JWT is signed with a static secret key ("secretKey"), which means that if this key is compromised, an attacker could forge JWTs and impersonate any user.

This vulnerability could lead to a business information leak, as an attacker could potentially gain access to any user's username and password.

Steps

  • Do not include sensitive information such as username and password in the JWT payload.
  • Instead, use the JWT to store a unique identifier for the user, such as user ID.
  • When the user logs in, authenticate the credentials and generate a session token.
  • Store the session token securely on the server side, such as in a session store or database.
  • Return the session token to the client as a response.
  • Do not store the session token in a cookie as it can be vulnerable to attacks.
  • Instead, use secure HTTP-only cookies to store a session identifier.
  • Ensure that the session identifier is unique and not easily guessable.
  • When the client makes subsequent requests, include the session identifier in the request headers.
  • On the server side, validate the session identifier and retrieve the associated user information.
  • Do not rely solely on the session identifier for authorization. Perform additional authorization checks to ensure the user has the necessary permissions.
  • Implement proper access controls and authentication mechanisms to protect sensitive business information.

Compliant code

import play.api.mvc._
import pdi.jwt._
import java.util.UUID

class UserController extends Controller {
def login = Action { request =>
val username = request.body.asFormUrlEncoded.get("username")(0)
val password = request.body.asFormUrlEncoded.get("password")(0)

// Authenticate the user
val user = authenticate(username, password)
if (user != null) {
// Generate a unique session identifier
val sessionId = UUID.randomUUID().toString

// Store the session identifier and user information on the server side
storeSession(sessionId, user)

// Return the session identifier to the client
Ok.withHeaders("X-Session-ID" -> sessionId)
} else {
Unauthorized
}
}

def authenticate(username: String, password: String): User = {
// Implement user authentication
}

def storeSession(sessionId: String, user: User): Unit = {
// Implement session storage
}
}

The updated code no longer includes sensitive information such as the username and password in the JWT payload. Instead, it uses the JWT to store a unique identifier for the user, such as a user ID.

When the user logs in, the code authenticates the credentials and generates a session token. This session token is stored securely on the server side, such as in a session store or database. The session token is then returned to the client as a response in the headers.

The code does not store the session token in a cookie as it can be vulnerable to attacks. Instead, it uses secure HTTP-only cookies to store a session identifier. The session identifier is unique and not easily guessable.

When the client makes subsequent requests, it includes the session identifier in the request headers. On the server side, the code validates the session identifier and retrieves the associated user information.

The code does not rely solely on the session identifier for authorization. It performs additional authorization checks to ensure the user has the necessary permissions. It also implements proper access controls and authentication mechanisms to protect sensitive business information.

References