Ssrf Server Side with Hmac Signatures
How SSRF Server-Side Manifests in HMAC Signatures
Server-Side Request Forgery (SSRF) attacks targeting HMAC-signed APIs exploit the trust relationship between services. When an API accepts HMAC signatures for authentication but processes user-controlled URLs, attackers can trick the server into making requests to internal services using the victim's credentials.
The vulnerability typically manifests when HMAC verification occurs before URL validation. Consider a payment processing API that signs webhook URLs:
const crypto = require('crypto');
const hmac = crypto.createHmac('sha256', process.env.HMAC_SECRET);
const signedUrl = `${url}?sig=${hmac.update(url).digest('hex')}`;
An attacker can modify the URL parameter to target internal services:
// Attacker crafts: http://localhost:8080/internal-api?data=secret
// Server processes: GET http://localhost:8080/internal-api?data=secret
// HMAC signature is valid for the modified URL!
Common HMAC SSRF patterns include:
- Webhook processing where HMAC verifies the callback URL before making the request
- API gateways that sign requests but don't validate destination hosts
- Service-to-service communication where internal endpoints trust HMAC signatures
- Cloud metadata service access via http://169.254.169.254
The attack succeeds because the HMAC signature validates the URL's integrity, but the server still processes the malicious destination. This creates a trust boundary violation where external attackers can abuse internal service authentication.
HMAC Signatures-Specific Detection
Detecting HMAC-based SSRF requires examining both the signing logic and request handling. Key indicators include:
| Symptom | Detection Method | Evidence |
|---|---|---|
| URL parameter in HMAC input | Static code analysis | Signature generation includes full URL |
| Missing host validation | Dynamic scanning | Requests succeed to localhost/169.254 |
| Service-to-service HMAC usage | Architecture review | Internal endpoints trust external HMAC |
middleBrick's HMAC-specific SSRF detection includes:
middlebrick scan https://api.example.com/webhook \
--test-ssrf \
--hmac-secret YOUR_SECRET_KEY \
--hmac-algorithm sha256
The scanner probes for:
- Localhost and RFC 1918 address access
- Cloud metadata endpoints (169.254.169.254)
- Internal service discovery via DNS rebinding
- Time-based SSRF to detect blind vulnerabilities
middleBrick analyzes the OpenAPI spec to identify HMAC signature patterns:
GET /webhook?data={data}&sig={signature}
Authorization: HMAC {signature}
The scanner tests whether URL manipulation bypasses HMAC verification and whether the server makes requests to attacker-specified destinations.
HMAC Signatures-Specific Remediation
Effective remediation requires defense-in-depth approaches specific to HMAC implementations:
1. Destination Validation Before HMAC Verification
const { URL } = require('url');
function validateDestination(urlString) {
const url = new URL(urlString);
// Block private networks
const privateBlocks = [
/^127\./,
/^10\./,
/^172\.(1[6-9]|2[0-9]|3[0-1])\./,
/^192\.168\./,
/^169\.254\./
];
if (privateBlocks.some(regex => regex.test(url.hostname))) {
throw new Error('Private network access denied');
}
// Optional: Allowlist trusted domains
const trustedDomains = ['api.example.com', 'webhook.example.com'];
if (!trustedDomains.includes(url.hostname)) {
throw new Error('Untrusted domain');
}
return true;
}
// Process request
const signedUrl = req.query.url;
try {
validateDestination(signedUrl);
// Only verify HMAC after validation
verifyHmacSignature(signedUrl, req.query.sig);
// Safe to make request
await makeWebhookRequest(signedUrl);
} catch (error) {
res.status(400).json({ error: error.message });
}
2. Separate URL Validation from Signature Generation
// BAD: URL in signature input
const badHmac = crypto.createHmac('sha256', secret)
.update(`${url}?data=${data}`)
.digest('hex');
// GOOD: Separate validation
function createSafeHmac(url, data, secret) {
const urlObj = new URL(url);
// Validate URL structure
if (!urlObj.protocol.startsWith('http')) {
throw new Error('Invalid protocol');
}
// Validate domain allowlist
if (!isTrustedDomain(urlObj.hostname)) {
throw new Error('Untrusted domain');
}
// Sign only the data, not the URL
return crypto.createHmac('sha256', secret)
.update(data)
.digest('hex');
}
3. Use HMAC for Data Integrity, Not URL Trust
// Sign only the payload, validate URL separately
const payload = JSON.stringify({ data, timestamp });
const signature = crypto.createHmac('sha256', secret)
.update(payload)
.digest('hex');
// Include URL as metadata, not in signature
const message = { url: trustedUrl, payload, signature };
4. Implement Time-Bound Signatures
function createTimedHmac(data, secret, ttl = 300000) {
const timestamp = Date.now();
const payload = `${data}.${timestamp}`;
const signature = crypto.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return { signature, timestamp };
}
function verifyTimedHmac(data, signature, timestamp, secret) {
const elapsed = Date.now() - timestamp;
if (elapsed > 300000) {
throw new Error('Signature expired');
}
const expected = crypto.createHmac('sha256', secret)
.update(`${data}.${timestamp}`)
.digest('hex');
if (!crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
)) {
throw new Error('Invalid signature');
}
}
These patterns prevent attackers from leveraging HMAC signatures to access internal services while maintaining the integrity benefits of HMAC authentication.