HIGH ssrf server sideexpresshmac signatures

Ssrf Server Side in Express with Hmac Signatures

Ssrf Server Side in Express with Hmac Signatures — how this specific combination creates or exposes the vulnerability

Server-side request forgery (SSRF) in an Express service that uses HMAC signatures can arise when a client-supplied URL is used both to drive internal HTTP requests and to validate an HMAC-based integrity check. If the signature is computed over a subset of parameters (e.g., a URL path or a selected set of query keys) but the runtime logic uses a different, broader set to decide where to forward, an attacker can supply a URL that passes signature verification yet directs the server to an internal or unexpected endpoint. Consider an Express route that expects a resource ID and an HMAC over that ID; if the server then appends the ID to a base URL chosen server-side and forwards the request, the attacker-supplied ID can be crafted to change the effective target (e.g., injecting metadata service IPs like 169.254.169.254). The HMAC does not protect the server’s outbound request destination; it only validates the client input under the assumptions made at signing time. Common patterns that exacerbate this include failing to normalize URLs before signing, allowing open redirects, or trusting user input for host resolution without strict allowlists. Because the scan tests unauthenticated attack surfaces, an SSRF vector reachable without credentials can be discovered through input validation and SSRF-specific checks, highlighting how a seemingly integrity-protected endpoint can still lead to unintended internal network interactions.

Hmac Signatures-Specific Remediation in Express — concrete code fixes

Remediation centers on ensuring the data used to compute the HMAC and the data used to make runtime routing decisions are bound together and validated against a strict allowlist. Do not rely on the HMAC alone to guarantee that a forwarded request targets an expected destination; instead, decouple the identifier from the target host and path, and enforce allowlists for protocols and hosts. Below are two concrete Express examples: a vulnerable pattern and a hardened pattern.

Vulnerable pattern to avoid

const crypto = require('crypto');
const axios = require('axios');
const express = require('express');
const app = express();

app.get('/resource', (req, res) => {
  const { id, signature } = req.query;
  const expected = crypto.createHmac('sha256', 'secret').update(id).digest('hex');
  if (signature !== expected) {
    return res.status(401).send('Invalid signature');
  }
  // Dangerous: attacker-controlled `id` influences the target URL
  const target = `https://api.partner.com/data/${id}`;
  axios.get(target).then(r => res.send(r.data)).catch(e => res.status(500).send('error'));
});

Hardened remediation with allowlist and fixed target

const crypto = require('crypto');
const axios = require('axios');
const express = require('express');
const app = express();

const ALLOWED_KINDS = new Set(['profile', 'settings', 'metrics']);
const FIXED_BASE = 'https://api.partner.com/v1';

function signPayload(payload) {
  return crypto.createHmac('sha256', 'secret').update(payload).digest('hex');
}

app.get('/resource', (req, res) => {
  const { kind, id, signature } = req.query;
  // 1) Validate kind against an allowlist
  if (!ALLOWED_KINDS.has(kind)) {
    return res.status(400).send('Invalid kind');
  }
  // 2) Validate ID format strictly (e.g., integer or UUID pattern)
  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id)) {
    return res.status(400).send('Invalid id');
  }
  // 3) Recompute signature over the same canonical payload used at runtime
  const payload = `${kind}:${id}`;
  const expected = signPayload(payload);
  if (signature !== expected) {
    return res.status(401).send('Invalid signature');
  }
  // 4) Build the target from fixed base + kind + id; do not trust host from client
  const target = `${FIXED_BASE}/${kind}/${id}`;
  return axios.get(target).then(r => res.send(r.data)).catch(e => res.status(500).send('error'));
});

Key practices illustrated:

  • Sign a canonical representation that includes both the kind and the ID, so tampering with either breaks verification.
  • Use an allowlist for kind to prevent protocol smuggling or host override.
  • Validate id with a strict pattern (e.g., UUID) to avoid path traversal or injection.
  • Construct the outbound URL from a fixed base and validated components, never by concatenating user input directly into a URL provided by the client.
  • Keep the HMAC secret out of source control and consider rotating it via environment variables with restricted access.

Even when HMACs are used, SSRF risks persist if the server’s outbound destination is derived from unchecked user input. The scans available through the middleBrick ecosystem can help detect such misconfigurations by testing unauthenticated attack surfaces. If you want to automate verification across endpoints, the CLI tool can be integrated into scripts with middlebrick scan <url>, and the GitHub Action can enforce a minimum security score in CI/CD pipelines.

Frequently Asked Questions

Can HMAC signatures alone prevent SSRF in Express APIs?
No. HMACs verify integrity of client-supplied data but do not restrict the server's outbound request destination. You must pair signatures with allowlists, strict input validation, and fixed target construction to mitigate SSRF.
How does middleBrick help detect SSRF risks in Express services?
middleBrick runs unauthenticated black-box checks including input validation and SSRF-specific tests against your endpoint. It returns a security risk score and prioritized findings with remediation guidance, without requiring credentials or agents.