HIGH ssrfexpresshmac signatures

Ssrf in Express with Hmac Signatures

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

Server-Side Request Forgery (SSRF) in Express applications that use HMAC signatures involves a mismatch between trusted computation and external data handling. An HMAC signature is typically generated over a request’s parameters, timestamp, and a shared secret to ensure integrity and origin authenticity. If the application validates the signature but then uses unsigned or attacker-controlled data to form a downstream URL, the signature can give a false sense of security while the request reaches an unintended internal endpoint.

Consider an Express route that fetches a remote resource. The client provides a URL and an HMAC signature computed over the URL and a timestamp. The server verifies the signature, then uses the provided URL to perform an HTTP request. Because the server made the request, SSRF can occur if the URL points to internal services such as http://127.0.0.1:6379 or metadata services like http://169.254.169.254. The HMAC check passes, but the server’s outbound request is still directed to a sensitive internal host, exposing internal infrastructure or cloud metadata to an attacker who can supply the malicious URL and valid signature.

In practice, SSRF with HMAC signatures can be triggered via query parameters, headers, or the request body when the server uses these to construct the downstream request. For example, an endpoint that accepts a next parameter for redirection or a url parameter for proxying can lead to SSRF if the server trusts the signature without additional allowlisting of target hosts or ports. Attack patterns include reading instance metadata, scanning internal networks, or abusing internal protocols that are not exposed externally. Even with a valid HMAC, the server must treat the input URL as untrusted and enforce strict allowlists to prevent unintended internal calls.

An example vulnerable Express route demonstrates the issue. The client computes an HMAC over the URL and timestamp and sends them in headers. The server verifies the HMAC and then uses the URL directly to make a request. Because the URL is not restricted to a safe set of domains, an attacker can supply an internal address that the server resolves and requests.

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

const SHARED_SECRET = process.env.SHARED_SECRET;

function verifyHmac(url, timestamp, receivedHmac) {
  const hmac = crypto.createHmac('sha256', SHARED_SECRET);
  hmac.update(url + timestamp);
  const expected = hmac.digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(receivedHmac));
}

app.get('/fetch', (req, res) => {
  const { url } = req.query;
  const timestamp = req.headers['x-timestamp'];
  const receivedHmac = req.headers['x-hmac'];

  if (!verifyHmac(url, timestamp, receivedHmac)) {
    return res.status(401).send('Invalid signature');
  }

  // SSRF risk: url is not validated beyond HMAC
  axios.get(url)
    .then(response => res.send(response.data))
    .catch(err => res.status(500).send('Error'));
});

app.listen(3000);

In this example, an attacker who knows or guesses the HMAC algorithm and shared secret (or exploits weak secret management) can craft a valid signature for an internal URL. The server verifies the signature and proceeds to request the internal host, leading to SSRF. Even if the secret is strong, the lack of host allowlisting means any validly signed URL is accepted, which exposes internal endpoints.

Hmac Signatures-Specific Remediation in Express — concrete code fixes

To remediate SSRF when HMAC signatures are used, treat all external input as hostile and enforce strict allowlisting in addition to signature verification. Signature validation proves the request has not been tampered with, but it does not authorize the target resource. You must ensure the URL points only to approved domains and ports, and avoid forwarding user input directly to network calls without validation.

Implement a host allowlist and normalize the URL before use. Parse the URL to confirm the hostname and port are within an approved set. Reject URLs that use non-HTTP(S) schemes or contain credentials. Combine this with signature verification to ensure only intended endpoints are called.

Below is a secure Express route that validates HMAC and enforces an allowlist for hosts. The URL is parsed, the host is checked against a predefined set, and only then is the request made. This approach mitigates SSRF while preserving the integrity guarantees provided by HMAC signatures.

const crypto = require('crypto');
const express = require('express');
const axios = require('axios');
const { URL } = require('url');
const app = express();

const SHARED_SECRET = process.env.SHARED_SECRET;
const ALLOWED_HOSTS = new Set(['api.example.com', 'data.example.com']);

function verifyHmac(url, timestamp, receivedHmac) {
  const hmac = crypto.createHmac('sha256', SHARED_SECRET);
  hmac.update(url + timestamp);
  const expected = hmac.digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(receivedHmac));
}

function isAllowedHost(hostname) {
  return ALLOWED_HOSTS.has(hostname);
}

app.get('/fetch', (req, res) => {
  const { url } = req.query;
  const timestamp = req.headers['x-timestamp'];
  const receivedHmac = req.headers['x-hmac'];

  if (!url || !timestamp || !receivedHmac) {
    return res.status(400).send('Missing parameters');
  }

  if (!verifyHmac(url, timestamp, receivedHmac)) {
    return res.status(401).send('Invalid signature');
  }

  let parsedUrl;
  try {
    parsedUrl = new URL(url);
  } catch (err) {
    return res.status(400).send('Invalid URL');
  }

  if (!isAllowedHost(parsedUrl.hostname)) {
    return res.status(403).send('Host not allowed');
  }

  // Proceed only if host is allowlisted and signature is valid
  axios.get(parsedUrl.toString())
    .then(response => res.send(response.data))
    .catch(err => res.status(500).send('Error'));
});

app.listen(3000);

Additional hardening includes rejecting non-HTTP(S) schemes, normalizing the URL to prevent path traversal or encoding tricks, and logging suspicious attempts. For production, rotate the shared secret periodically and monitor for repeated signature verification failures. These measures ensure that HMAC signatures contribute to integrity without inadvertently enabling SSRF through unchecked URL inputs.

Related CWEs: ssrf

CWE IDNameSeverity
CWE-918Server-Side Request Forgery (SSRF) CRITICAL
CWE-441Unintended Proxy or Intermediary (Confused Deputy) HIGH

Frequently Asked Questions

Can HMAC signatures alone prevent SSRF in Express APIs?
No. HMAC signatures verify integrity and origin but do not restrict the target host. You must combine signature verification with host allowlisting and input validation to prevent SSRF.
What additional checks should be added to the Express SSRF mitigation example?
Add URL scheme allowlisting (only http/https), port restrictions, path normalization, and rejection of URLs with embedded credentials. Consider using a dedicated proxy with strict routing instead of direct outbound requests.