Arp Spoofing in Strapi with Hmac Signatures
Arp Spoofing in Strapi with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Arp spoofing is a Layer 2 network attack where an adversary sends falsified ARP messages to associate their MAC address with the IP of a legitimate host, such as the server running Strapi. In a typical Strapi deployment, clients authenticate and obtain resources by presenting Hmac signatures in request headers. These signatures are usually computed over a canonical string that includes the HTTP method, path, timestamp, and a secret key. When an attacker performs arp spoofing on the network segment between a client and the Strapi server, they can intercept, modify, or replay these signed requests. Because the Hmac verification on the server depends only on the data in the request and the shared secret — and not on a cryptographically bound transport context — the attacker may forward captured, valid Hmac-signed requests to the server (a replay) or alter non-idempotent payloads while keeping the signature valid if the server does not enforce strict freshness and uniqueness checks. This exposes the vulnerability: the integrity guarantee of Hmac signatures does not inherently protect against replay or manipulation on a compromised local network, since the cryptographic integrity is independent of link-layer authenticity. Strapi’s endpoints that rely solely on Hmac without additional transport-layer protections or anti-replay mechanisms can be abused to perform unauthorized actions under the identity of a legitimate client.
Hmac Signatures-Specific Remediation in Strapi — concrete code fixes
To mitigate arp spoofing risks when using Hmac signatures in Strapi, you must ensure that each signed request includes verifiable uniqueness and freshness constraints, and that the server validates these before processing business logic. Below are concrete Strapi customization examples that demonstrate these protections.
Example 1: Hmac verification with nonce and timestamp validation
Extend Strapi’s request handling to require a nonce and an ISO timestamp in headers, and verify them before checking the Hmac. Store used nonces temporarily (e.g., in Redis) to prevent reuse.
// File: src/middlewares/hmac-verify/config.js
module.exports = {
replayCacheTtlSeconds: 300, // 5 minutes
};
// File: src/middlewares/hmac-verify/middleware.js
const crypto = require('crypto');
const { replayCacheTtlSeconds } = require('./config');
const nonceCache = new (require('node-cache'))({ stdTTL: replayCacheTtlSeconds });
module.exports = (config, { strapi }) => {
return async (ctx, next) => {
const { header } = ctx.request;
const receivedSignature = header['x-hmac-signature'];
const nonce = header['x-nonce'];
const timestamp = header['x-timestamp'];
const sharedSecret = process.env.HMAC_SHARED_SECRET;
if (!receivedSignature || !nonce || !timestamp) {
ctx.status = 400;
ctx.body = { error: 'Missing required headers' };
return;
}
// Reject stale requests (>2 minutes)
const requestTime = new Date(timestamp).getTime();
const now = Date.now();
if (Math.abs(now - requestTime) > 2 * 60 * 1000) {
ctx.status = 400;
ctx.body = { error: 'Timestamp out of acceptable window' };
return;
}
// Prevent replay attacks using nonce cache
if (nonceCache.has(nonce)) {
ctx.status = 403;
ctx.body = { error: 'Replay attack detected' };
return;
}
const payloadToSign = [
ctx.request.method.toUpperCase(),
ctx.request.url.split('?')[0],
timestamp,
nonce,
ctx.request.rawBody?.toString('utf8') || '',
].join('\n');
const expectedSignature = crypto
.createHmac('sha256', sharedSecret)
.update(payloadToSign)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expectedSignature), Buffer.from(receivedSignature))) {
ctx.status = 401;
ctx.body = { error: 'Invalid signature' };
return;
}
nonceCache.set(nonce, true);
await next();
};
};
Example 2: Strapi route policy using the middleware
Apply the middleware selectively to sensitive routes in your Strapi plugin’s src/api/admin/policy.js or via a custom router configuration.
// File: src/admin/policy.js
const hmacVerify = require('../middlewares/hmac-verify/middleware');
module.exports = {
policies: {
'/admin/api/content-manager/explorer': [hmacVerify],
'/admin/api/users': [hmacVerify],
},
};
Additional recommendations
- Always use HTTPS to prevent passive eavesdropping and ARP cache poisoning at the network level.
- Keep your shared secret in environment variables and rotate it periodically.
- Consider adding IP allowlisting for admin endpoints when possible, though this is complementary, not a replacement for Hmac replay controls.