Concurrent sessions control bypass - TypeScript

Concurrent sessions control bypass - TypeScript

Need

Enforcement of concurrent session control

Context

  • Usage of TypeScript 3.0 for static typing and advanced features in JavaScript development
  • Usage of Express for building web applications and handling HTTP requests
  • Usage of express-session for managing session data in Express applications

Description

Non compliant code

        import express from 'express';
import session from 'express-session';

const app = express();

app.use(session({
  secret: 'secret-key',
  resave: false,
  saveUninitialized: true,
}));

app.get('/', (req, res) => {
  if (req.session.loggedIn) {
    res.send('Welcome to the application!');
  } else {
    res.send('Please log in.');
  }
});

app.post('/login', (req, res) => {
  // Simulating a successful login
  req.session.loggedIn = true;
  res.send('Logged in successfully!');
});

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

The vulnerability in the provided code is a concurrent sessions control bypass.

In the code, the application uses the Express framework and the express-session middleware to handle sessions. The session middleware is configured with a secret key and default options.

The vulnerability lies in the fact that there is no mechanism in place to invalidate previous sessions when a user logs in from a new location. This means that if a user logs in from one location and then logs in again from another location without logging out from the first session, both sessions will remain active.

This vulnerability can be exploited by an attacker who can gain access to the application with the same user credentials from multiple locations simultaneously. This can lead to a loss of traceability as the application will not be able to differentiate between the legitimate user and the attacker.

To mitigate this vulnerability, it is recommended to immediately invalidate the previous session when a user logs in from a new location. This can be done by implementing a mechanism to check for existing active sessions and terminating them when a new session is created.

Steps

  • Install the 'connect-redis' package to store session data in Redis.
  • Import the 'RedisStore' from 'connect-redis' package.
  • Create a Redis client instance using the 'redis' package.
  • Update the session configuration to use the RedisStore and the Redis client instance.
  • Add a check in the '/login' route to invalidate previous sessions for the same user.
  • Test the application to ensure the concurrent session control is working as expected.

Compliant code

        import express from 'express';
import session from 'express-session';
import redis from 'redis';
import connectRedis from 'connect-redis';

const app = express();
const RedisStore = connectRedis(session);
const redisClient = redis.createClient();

app.use(session({
  secret: 'secret-key',
  resave: false,
  saveUninitialized: true,
  store: new RedisStore({ client: redisClient }),
}));

app.get('/', (req, res) => {
  if (req.session.loggedIn) {
    res.send('Welcome to the application!');
  } else {
    res.send('Please log in.');
  }
});

app.post('/login', (req, res) => {
  // Simulating a successful login
  req.session.loggedIn = true;

  // Invalidate previous sessions for the same user
  req.session.regenerate(() => {
    res.send('Logged in successfully!');
  });
});

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

The fixed code addresses the vulnerability by implementing a session management mechanism that invalidates previous sessions when a user logs in from a new location. Here's an explanation of the changes made:

1. The code imports the necessary modules: express, express-session, redis, and connect-redis. These modules are used to handle session management and store session data in Redis.

2. The code creates an instance of the express application and initializes a Redis store and a Redis client.

3. The express-session middleware is added to the application using app.use(). It is configured with a secret key, resave set to false to prevent unnecessary session saves, saveUninitialized set to true to save uninitialized sessions, and the Redis store created earlier.

4. The code defines a route handler for the root path ("/"). If the loggedIn property is set in the session, it sends a "Welcome to the application!" message. Otherwise, it sends a "Please log in." message.

5. The code defines a route handler for the "/login" path. When a successful login occurs, it sets the loggedIn property in the session to true.

6. To invalidate previous sessions for the same user, the code calls req.session.regenerate() after a successful login. This function regenerates the session, generating a new session ID and deleting the old session data.

7. Finally, the code starts the server and listens on port 3000.

By regenerating the session after a successful login, the fixed code ensures that any previous sessions for the same user are invalidated. This prevents concurrent access to the application with the same user, addressing the vulnerability.

References