Time Of Check Time Of Use in Express with Hmac Signatures
Time Of Check Time Of Use in Express with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Time Of Check Time Of Use (TOCTOU) is a class of race condition where the state of a resource changes between a security check and the subsequent use of that resource. In Express applications that use Hmac Signatures for request authentication, a TOCTOU vulnerability can occur when signature verification and the action protected by that signature are not performed as a single, atomic operation. An attacker can exploit the window between the check and the use by altering the resource or the request parameters after the signature is validated but before the server acts on it.
Consider an endpoint that accepts an Hmac-Signature header, verifies the signature against a shared secret, and then processes a payment or updates a user record. If the server first validates the signature and then reads mutable request data (such as a JSON body or query parameters) to perform the action, an attacker can intercept or mutate that data in flight. Because the signature was computed over the original payload, the server may still consider the request valid, leading to unauthorized operations such as changing the transaction amount or the target user ID. This pattern is common when middleware parses the body after the authentication layer, or when route handlers perform additional lookups based on client-supplied identifiers that were not covered by the Hmac scope.
The vulnerability is exacerbated when the Hmac is computed over only a subset of the request, such as selected headers or a partial URL, while the server uses other unchecked inputs in the critical section. For example, an Hmac might cover the HTTP method, path, and a timestamp, but if the handler later uses a user-controlled ID from the request body to look up and modify a database record, the signature does not protect that ID. An attacker can modify the ID between verification and database update, leading to IDOR or unauthorized data access. Because the signature validation passes, the server treats the request as legitimate, and the race condition is triggered when the mutable state is used.
In distributed or high-concurrency environments, the risk increases as timing variations and asynchronous processing can widen the check-to-use window. An attacker may send many requests with valid Hmac Signatures while concurrently modifying the underlying resource, increasing the likelihood that the server will act on stale or tampered data. The key security principle is to bind the signature to all inputs that will be used in the critical operation and to treat verification and usage as a single, indivisible step to prevent state changes in between.
Hmac Signatures-Specific Remediation in Express — concrete code fixes
To mitigate TOCTOU with Hmac Signatures in Express, ensure that the data used to compute and verify the signature is immutable for the duration of the request and that verification and usage happen atomically. Prefer signing and verifying the entire request payload and all relevant parameters, and avoid additional lookups based on unchecked inputs. Below are concrete code examples demonstrating secure patterns.
Example 1: Verifying Hmac over the full request body and using the data directly
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(express.json());
const SHARED_SECRET = process.env.HMAC_SECRET;
function verifyHmac(req, res, next) {
const signature = req.headers['x-hmac-signature'];
if (!signature) {
return res.status(401).json({ error: 'Missing signature' });
}
const payload = JSON.stringify(req.body);
const expected = crypto
.createHmac('sha256', SHARED_SECRET)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).json({ error: 'Invalid signature' });
}
next();
}
app.post('/transfer', verifyHmac, (req, res) => {
// Use req.body directly after verification; no additional ID lookup from client
const { fromAccount, toAccount, amount } = req.body;
if (!fromAccount || !toAccount || typeof amount !== 'number' || amount <= 0) {
return res.status(400).json({ error: 'Invalid payload' });
}
// Perform the transfer using only the signed body fields
// ...
res.json({ status: 'ok' });
});
Example 2: Including critical identifiers in the Hmac scope
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(express.json());
const SHARED_SECRET = process.env.HMAC_SECRET;
app.post('/resource/:id', (req, res, next) => {
// Include path parameter and body in the signature verification
const payload = JSON.stringify({
pathId: req.params.id,
body: req.body
});
const expected = crypto
.createHmac('sha256', SHARED_SECRET)
.update(payload)
.digest('hex');
const signature = req.headers['x-hmac-signature'];
if (!signature || !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).json({ error: 'Invalid signature' });
}
next();
}, (req, res) => {
// Now safe to use req.params.id and req.body knowing they were signed
const { action } = req.body;
// ...
res.json({ status: 'ok' });
});
Best practices summary
- Compute the Hmac over all inputs that will be used in the critical section, including path parameters, selected headers, and the request body.
- Perform verification and the sensitive operation in the same handler or middleware chain without intervening lookups based on mutable data.
- Use
crypto.timingSafeEqualto compare signatures to prevent timing attacks on the MAC. - Avoid allowing the client to dictate identifiers that are used for database operations after signature verification; if necessary, map signed identifiers to internal IDs server-side within the same atomic step.