Skip to main content

Insecure functionality - Session management

Need

Secure session management

Context

  • Usage of TypeScript for statically typed JavaScript development
  • Usage of Express for building web applications and APIs
  • Usage of jsonwebtoken for generating and verifying JSON Web Tokens (JWT)

Description

Non compliant code

import express from 'express';
import jwt from 'jsonwebtoken';

const app = express();

// Secret key for JWT token
const secretKey = 'mysecretkey';

// Route to generate a new session token
app.get('/login', (req, res) => {
const user = {
id: 1,
username: 'example_user',
role: 'admin'
};

// Generate a new JWT token with a 1-hour expiration time
const token = jwt.sign(user, secretKey, { expiresIn: '1h' });

res.json({ token });
});

// Route to perform a query with an expired token
app.get('/query', (req, res) => {
const token = req.headers.authorization?.split(' ')[1];

try {
// Verify the token and extract the payload
const payload = jwt.verify(token, secretKey);

// Perform the query using the payload data
// ...

res.json({ message: 'Query successful' });
} catch (error) {
res.status(401).json({ message: 'Invalid or expired token' });
}
});

app.listen(3000, () => {
console.log('Server is running on port 3000');
});

The vulnerability in this code is related to insecure session management. Specifically, it allows for the modification of the expiration time of an expired session token, making it possible to reuse the token and continue querying the application.

In the code, the /login route generates a new session token using the jwt.sign function from the jsonwebtoken library. The token is signed with a secret key and has an expiration time of 1 hour.

The /query route is used to perform a query with a token provided in the request headers. The token is extracted from the Authorization header and verified using the jwt.verify function. If the token is valid and has not expired, the query is performed and a successful response is sent. Otherwise, an error response is returned.

However, the vulnerability lies in the fact that the code does not check if the token has expired before performing the query. This means that even if the token has expired, it can still be used to make queries to the application. An attacker can modify the expiration time of an expired token and continue to query the application with it.

To fix this vulnerability, the code should include a check to ensure that expired tokens are not reused in future requests. Once a token has expired, it should not be accepted for further queries.

Steps

  • Remove the route to generate a new session token (/login) as it is not necessary for this vulnerability fix.
  • Modify the route to perform a query with an expired token (/query) to check if the token is expired before performing the query.
  • If the token is expired, return an error response indicating that the token is invalid or expired.
  • If the token is not expired, proceed with performing the query using the payload data.
  • Update the code comments to reflect the changes made.

Compliant code

import express from 'express';
import jwt from 'jsonwebtoken';

const app = express();

// Secret key for JWT token
const secretKey = 'mysecretkey';

// Route to perform a query with a token
app.get('/query', (req, res) => {
const token = req.headers.authorization?.split(' ')[1];

try {
// Verify the token and extract the payload
const payload = jwt.verify(token, secretKey, { ignoreExpiration: true });

// Check if the token is expired
const isTokenExpired = Date.now() >= payload.exp * 1000;

if (isTokenExpired) {
res.status(401).json({ message: 'Invalid or expired token' });
} else {
// Perform the query using the payload data
// ...

res.json({ message: 'Query successful' });
}
} catch (error) {
res.status(401).json({ message: 'Invalid or expired token' });
}
});

app.listen(3000, () => {
console.log('Server is running on port 3000');
});

The fixed code addresses the vulnerability related to insecure session management by implementing proper token expiration handling. Here's how the code works:

  1. The code imports the necessary modules, including Express for creating the server and JWT for token handling.

  2. The app object is created using Express.

  3. A secret key is defined for JWT token generation and verification.

  4. The /query route is created to handle queries with a token.

  5. Inside the route handler, the token is extracted from the Authorization header using optional chaining (?.) and splitting the header value.

  6. The code then attempts to verify the token and extract the payload using the jwt.verify method. The ignoreExpiration option is set to true to bypass the default token expiration check.

  7. After verifying the token, the code checks if the token is expired by comparing the current time (Date.now()) with the expiration time (payload.exp * 1000). The expiration time is multiplied by 1000 to convert it from seconds to milliseconds.

  8. If the token is expired, the server responds with a 401 status code and a JSON message indicating an invalid or expired token.

  9. If the token is not expired, the code proceeds to perform the query using the payload data. This is where you would add your logic to handle the query.

  10. Finally, if the query is successful, the server responds with a JSON message indicating the success.

  11. The code also includes a catch block to handle any errors that occur during token verification. In case of an error, the server responds with a 401 status code and a JSON message indicating an invalid or expired token.

  12. The server is then set to listen on port 3000.

In summary, the fixed code properly verifies the token, checks for token expiration, and handles expired tokens by returning an appropriate response. This ensures that expired tokens cannot be reused to continue querying the application.

References