Skip to main content

Security controls bypass or absence

Need

Implementation of robust security controls

Context

  • Usage of TypeScript for type-checking and compiling JavaScript code
  • Usage of Express for building web applications and APIs

Description

Non compliant code

import express from 'express';

const app = express();

const MAX_REQUESTS = 10;
const TIME_SLOT = 60 * 1000; // 1 minute

const requestCounts: { [ip: string]: number } = {};

app.use((req, res, next) => {
const ip = req.ip;

if (!requestCounts[ip]) {
requestCounts[ip] = 1;
} else {
requestCounts[ip]++;
}

if (requestCounts[ip] > MAX_REQUESTS) {
return res.status(429).send('Too Many Requests');
}

setTimeout(() => {
requestCounts[ip]--;
}, TIME_SLOT);

next();
});

app.get('/', (req, res) => {
res.send('Hello World!');
});

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

The vulnerability in this code is related to the absence of proper security controls to limit the number of requests that can be made by the same host within a defined time slot.

The code keeps track of the number of requests made by each IP address using the requestCounts object. However, it does not enforce any limit on the number of requests that can be made. Instead, it only checks if the number of requests exceeds the MAX_REQUESTS value and sends a response with a status code of 429 (Too Many Requests) if it does.

The issue is that even if the maximum number of requests is reached, the code still allows the requests to be processed. It does not prevent the requests from reaching the server and being processed, which means an attacker can still flood the server with multiple requests, potentially causing a denial-of-service (DoS) attack.

To mitigate this vulnerability, the code should implement a mechanism to limit the number of requests that can be made within a specific time slot. This can be achieved by using a rate-limiting algorithm or a middleware that enforces the limit and blocks or delays requests that exceed the threshold.

Steps

  • Implement rate limiting to limit the number of requests that can be made by the same host in defined time slots.
  • Create a middleware function that tracks the number of requests made by each IP address.
  • If the number of requests exceeds the maximum allowed, return a 'Too Many Requests' response with status code 429.
  • Use a setTimeout function to decrement the request count after the defined time slot has passed.
  • Apply the rate limiting middleware to all routes that need to be protected.
  • Test the application to ensure that the rate limiting is working as expected.

Compliant code

import express, { Request, Response, NextFunction } from 'express';

const app = express();

const MAX_REQUESTS = 10;
const TIME_SLOT = 60 * 1000; // 1 minute

const requestCounts: { [ip: string]: number } = {};

const rateLimitMiddleware = (req: Request, res: Response, next: NextFunction) => {
const ip = req.ip;

if (!requestCounts[ip]) {
requestCounts[ip] = 1;
} else {
requestCounts[ip]++;
}

if (requestCounts[ip] > MAX_REQUESTS) {
return res.status(429).send('Too Many Requests');
}

setTimeout(() => {
requestCounts[ip]--;
}, TIME_SLOT);

next();
};

app.use(rateLimitMiddleware);

app.get('/', (req: Request, res: Response) => {
res.send('Hello World!');
});

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

The fixed code addresses the vulnerability by implementing a rate limiting mechanism to limit the number of requests that can be made by the same host within a defined time slot.

The code uses the Express framework to create a server. It defines two constants: MAX_REQUESTS and TIME_SLOT. MAX_REQUESTS represents the maximum number of requests allowed within the time slot, and TIME_SLOT represents the duration of the time slot in milliseconds.

The code maintains a requestCounts object to keep track of the number of requests made by each IP address. The object is initialized as an empty object.

A rateLimitMiddleware function is defined as a middleware function that is executed for every incoming request. It takes three parameters: req (the request object), res (the response object), and next (a function to pass control to the next middleware).

Inside the rateLimitMiddleware function, the IP address of the incoming request is extracted from req.ip. If the IP address does not exist in the requestCounts object, it is added with an initial count of 1. Otherwise, the count is incremented by 1.

The code then checks if the count for the IP address exceeds the MAX_REQUESTS limit. If it does, a response with a status code of 429 (Too Many Requests) and a message is sent back to the client.

To ensure that the count is decremented after the time slot expires, a setTimeout function is used. It schedules a function to be executed after the specified time slot. Inside the function, the count for the IP address is decremented by 1.

Finally, the next function is called to pass control to the next middleware or route handler.

The rateLimitMiddleware function is added as a middleware using the app.use() method, ensuring that it is executed for every incoming request.

The app.get() method defines a route handler for the root path ("/") that simply sends a "Hello World!" response.

The app.listen() method starts the server on port 3000.

Overall, this fixed code implements rate limiting by keeping track of the number of requests made by each IP address and limiting the requests based on the defined maximum and time slot.

References