Arp Spoofing with Hmac Signatures
How ARP Spoofing Manifests in HMAC Signatures
ARP spoofing is a local network attack where an attacker sends forged ARP messages to link their MAC address with the IP of a legitimate server (like an API gateway). This enables a man-in-the-middle (MITM) position on the same broadcast domain. When APIs rely solely on HMAC signatures for request authentication without additional network-layer protections, this MITM position can critically undermine the security guarantees HMAC provides.
The core vulnerability arises when an HMAC-signed API request traverses an insecure network channel (e.g., plain HTTP). An attacker performing ARP spoofing can intercept, read, and modify the request before it reaches the backend. While the HMAC itself is cryptographically sound, its integrity protection is only effective if the request arrives unaltered. Common attack patterns include:
- Header/Parameter Injection: The attacker modifies headers (e.g.,
X-User-Id) or JSON body fields (e.g.,"amount":100to"amount":10000) after the client computed the HMAC but before the server verifies it. The server's HMAC validation will fail, but if the API incorrectly processes the request before HMAC verification (a logic flaw), the attack succeeds. - Replay Attacks: The attacker captures a valid, signed request (e.g., a fund transfer). Without a nonce or timestamp in the signed payload, they can replay this exact request later. ARP spoofing provides the initial interception point to capture the request.
- Stripping HMAC Headers: The attacker removes the
X-Signatureheader entirely. If the server treats missing signatures as unauthenticated but still processes the request (e.g., for a public endpoint), this might bypass intended controls.
Consider a vulnerable Node.js/Express API that checks HMAC after parsing the body:
app.post('/transfer', (req, res) => {
// VULNERABLE: Body already parsed and potentially used
const { amount, toAccount } = req.body; // Logic flaw: using data before auth
const signature = req.headers['x-signature'];
const expected = crypto.createHmac('sha256', SECRET)
.update(JSON.stringify(req.body))
.digest('hex');
if (signature !== expected) return res.status(401).send('Invalid signature');
// Process transfer with amount/toAccount
});An attacker on the same network via ARP spoofing could modify req.body.amount before the HMAC check, causing a logic error where the altered value is used despite a subsequent signature mismatch check.
HMAC Signatures-Specific Detection
Detecting this class of vulnerability requires analyzing both the HMAC implementation and the transport security. The key indicators are:
- Use of HTTP instead of HTTPS: HMAC signatures protect against tampering only if the channel is confidential and integrity-protected. Without TLS, network-level attackers can modify requests.
- Absence of nonces/timestamps in the signed payload: A static signature over static data is replayable. The signed string must include a unique, single-use value (nonce) or a short-lived timestamp.
- Order of operations flaws: Code that accesses or uses request parameters (body, headers, query) before verifying the HMAC signature is vulnerable to parameter injection via MITM.
- Weak hash algorithms: Use of SHA-1 or MD5 in
crypto.createHmacis deprecated and susceptible to collision attacks.
Scanning with middleBrick
middleBrick's unauthenticated black-box scanner tests the API's external attack surface. It can detect these issues by:
- Issuing requests over HTTP and observing if the API accepts HMAC-signed requests without redirecting to HTTPS, indicating mixed or insecure transport.
- Analyzing the OpenAPI/Swagger spec (if available) for security schemes like
apiKeyin headers withoutscheme: httpsor missingx-nonceparameters. - Performing active probing: sending a valid HMAC-signed request, then a modified version with an altered parameter (e.g., changing a numeric field), and checking if the server responds differently (e.g., 200 OK vs 401) before/after modification, which can indicate processing before verification.
- Checking for replayability by capturing a signed request and replaying it seconds later; if accepted, a nonce/timestamp is likely missing or improperly validated.
Example CLI scan to identify such issues:
middlebrick scan https://api.example.com/v1/transfer
# Output includes findings like:
# - [HIGH] HMAC signature used over HTTP (no TLS)
# - [MEDIUM] No nonce/timestamp in signed payload (replay risk)
# - [HIGH] Parameter processed before HMAC verification (potential injection)HMAC Signatures-Specific Remediation
Remediation must address both cryptographic implementation and transport security. The goal is to ensure that even if an attacker achieves ARP spoofing, they cannot forge valid requests or replay captured ones.
1. Enforce TLS (HTTPS) Everywhere
All API endpoints accepting HMAC-signed requests must require TLS. This prevents network-level tampering. Configure your server to redirect HTTP to HTTPS and use HSTS.
2. Include a Nonce or Timestamp in the Signed String
The data signed by the HMAC must include a unique, unpredictable nonce (e.g., UUID v4) or a tightly bounded timestamp (e.g., current time in seconds ± 30s). The server must store used nonces (or track recent timestamps) to reject replays.
3. Verify HMAC Before Any Request Processing
Never access req.body, req.params, or other derived data until after the HMAC is validated. Compute the expected signature from the raw request body (or canonicalized string) and compare using a constant-time function to prevent timing attacks.
4. Use Strong Hash Algorithms
Use SHA-256 or SHA-512. Avoid SHA-1/MD5.
Corrected Code Example (Node.js/Express)
const crypto = require('crypto');
const usedNonces = new Set(); // In production, use Redis with TTL
app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; } }));
app.post('/transfer', (req, res) => {
const signature = req.headers['x-signature'];
const nonce = req.headers['x-nonce'];
const timestamp = req.headers['x-timestamp'];
// 1. Check timestamp freshness (e.g., within 30 seconds)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > 30) {
return res.status(401).send('Timestamp out of range');
}
// 2. Check nonce uniqueness
if (usedNonces.has(nonce)) {
return res.status(401).send('Replay detected');
}
usedNonces.add(nonce);
setTimeout(() => usedNonces.delete(nonce), 60000); // Clean up after 60s
// 3. Compute HMAC over raw body + timestamp + nonce (canonical string)
const payload = req.rawBody.toString('utf8');
const signData = `${timestamp}:${nonce}:${payload}`;
const expected = crypto.createHmac('sha256', process.env.HMAC_SECRET)
.update(signData)
.digest('hex');
// 4. Constant-time comparison BEFORE using req.body
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
// 5. NOW safely parse and use the body
const { amount, toAccount } = JSON.parse(payload);
// ... process transfer
res.send('Transfer successful');
});Notes:
- The raw body is captured via Express's
verifyoption to avoid parsing before signature check. - The signed string includes
timestampandnonceto bind them to the payload. - Nonce is stored temporarily to prevent replay.
crypto.timingSafeEqualprevents timing attacks on the signature comparison.
With these fixes, even if an attacker performs ARP spoofing to intercept a request, they cannot modify it (TLS prevents tampering), and they cannot replay it (nonce/timestamp check). The HMAC's integrity guarantee is now meaningful.