Ssrf with Hmac Signatures
How SSRF Manifests in HMAC Signatures
Server-Side Request Forgery (SSRF) in HMAC signature verification systems creates a dangerous attack vector where attackers can trick servers into making unauthorized requests to internal resources. In HMAC-based API authentication, the signature verification process often requires fetching additional data or metadata to validate requests, creating opportunities for SSRF exploitation.
The most common SSRF manifestation occurs during key retrieval. When an API receives a request with an HMAC signature, it must verify the signature against the correct secret key. Many implementations fetch these keys from internal services or databases using URLs constructed from request parameters. An attacker can manipulate these URLs to access internal services:
# Vulnerable HMAC key retrieval pattern
def verify_hmac(request):
key_id = request.headers.get('X-Key-Id')
key_url = f"http://internal-keyservice/keys/{key_id}"
key_data = requests.get(key_url).text # SSRF vulnerability!
expected_signature = hmac.new(key_data.encode(), request.body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected_signature, request.headers.get('X-Signature'))
In this pattern, an attacker can supply a key_id like ../../../../etc/passwd or use an absolute URL to access internal services. The HMAC verification process will happily fetch the attacker-specified resource, potentially exposing sensitive internal data or triggering actions on internal systems.
Another critical SSRF vector appears in timestamp validation. Many HMAC implementations include timestamp-based replay protection, fetching current time from internal NTP servers or time services:
// Vulnerable timestamp validation
const verifyTimestamp = async (request) => {
const timestamp = request.headers.get('X-Timestamp');
const timeUrl = `http://internal-timeservice/current-time`;
const currentTime = await fetch(timeUrl).text(); // SSRF here
const age = Math.abs(currentTime - timestamp);
return age < 300; // valid if within 5 minutes
};
Attackers can manipulate the time service URL to access internal metadata services, configuration endpoints, or even cloud instance metadata endpoints (http://169.254.169.254 in AWS).
Webhook verification presents another SSRF opportunity. Some HMAC implementations verify webhook signatures by fetching the original content from the provided URL:
// Vulnerable webhook verification
func verifyWebhookSignature(payload []byte, signature string, url string) bool {
// SSRF: fetching content from attacker-controlled URL
resp, err := http.Get(url + "?verify=1")
if err != nil { return false }
defer resp.Body.Close()
originalPayload, _ := ioutil.ReadAll(resp.Body)
expected := computeHMAC(originalPayload, secretKey)
return subtle.ConstantTimeCompare([]byte(signature), []byte(expected)) == 1
}
This pattern allows attackers to provide URLs pointing to internal services, cloud metadata endpoints, or other sensitive resources, effectively using the HMAC verification service as a proxy to access internal data.
HMAC Signatures-Specific Detection
Detecting SSRF in HMAC signature systems requires specialized scanning that understands the specific patterns and endpoints these systems use. middleBrick's black-box scanning approach is particularly effective here, as it tests the actual runtime behavior without requiring source code access.
The detection process begins with identifying HMAC-specific endpoints. These typically include:
- API endpoints expecting
X-Signature,Authorization: HMAC, or similar headers - Webhook verification endpoints that accept URLs and signatures
- Key management endpoints that handle key IDs and secrets
- Timestamp validation endpoints used in replay protection
middleBrick scans these endpoints for SSRF indicators by:
- Testing URL parameter manipulation in key retrieval endpoints
- Attempting access to internal IP ranges (192.168.x.x, 10.x.x.x, 172.16-31.x.x)
- Probing cloud metadata endpoints (169.254.169.254, 169.254.170.2)
- Checking for SSRF in timestamp validation flows
- Testing webhook verification with internal URLs
During scanning, middleBrick constructs targeted payloads based on HMAC implementation patterns. For example, it tests key ID parameters with path traversal sequences, attempts to access internal services using the same base URLs the application uses, and verifies whether the system can be tricked into making requests to localhost or internal networks.
The scanner also examines response patterns for SSRF indicators. Successful SSRF attempts often result in:
- Unexpected response content (internal error pages, configuration data)
- Time delays indicating network requests to unreachable external hosts
- HTTP status codes from internal services (401, 403, 500 series)
- Headers or content from internal services leaking through
middleBrick's LLM/AI security module adds another layer of detection for AI-powered HMAC systems. If the API uses machine learning models for signature verification or anomaly detection, the scanner tests for SSRF in the model inference pipeline, where requests to fetch training data or model weights could be exploited.
The scanning results include specific findings with severity levels based on the potential impact. Critical findings indicate successful SSRF to internal services, high severity for cloud metadata access, and medium for localhost access attempts. Each finding includes the exact request that triggered the vulnerability and the response data that confirms the SSRF condition.
HMAC Signatures-Specific Remediation
Remediating SSRF in HMAC signature systems requires architectural changes and strict input validation. The most effective approach combines allowlist-based URL validation with internal service isolation.
For key retrieval endpoints, implement strict allowlisting of key identifiers and use internal service discovery instead of URL construction:
# Secure HMAC key retrieval
from typing import Optional
import hmac, hashlib
class SecureKeyService:
def __init__(self):
# Allowlist of valid key IDs
self.valid_key_ids = {'user123', 'service456', 'webhook789'}
# Internal key storage (in production, use secure key management)
self.keys = {
'user123': 'supersecretkey1',
'service456': 'anothersecretkey',
'webhook789': 'webhooksecretkey'
}
def get_key(self, key_id: str) -> Optional[str]:
# Validate key_id format and allowlist membership
if not key_id or not key_id.isalnum() or key_id not in self.valid_key_ids:
return None
# Return key directly without external requests
return self.keys.get(key_id)
def verify_signature(self, request: dict) -> bool:
key_id = request.headers.get('X-Key-Id')
signature = request.headers.get('X-Signature')
key = self.get_key(key_id)
if not key:
return False
# Compute expected signature
expected = hmac.new(key.encode(), request.body, hashlib.sha256).hexdigest()
# Use constant-time comparison to prevent timing attacks
return hmac.compare_digest(expected, signature)
This approach eliminates URL construction entirely, using internal key storage with strict validation. The allowlist prevents attackers from manipulating key IDs to access unauthorized keys or trigger internal requests.
For timestamp validation, use system time directly instead of fetching from internal services:
// Secure timestamp validation
const verifyTimestamp = (requestTimestamp, maxAgeSeconds = 300) => {
const currentTimestamp = Math.floor(Date.now() / 1000);
const age = Math.abs(currentTimestamp - requestTimestamp);
// Validate timestamp format and age
if (!Number.isInteger(requestTimestamp) || age > maxAgeSeconds) {
return false;
}
return true;
};
This eliminates the SSRF vector by removing external requests for time validation. The system clock provides sufficient accuracy for replay protection, and the strict integer validation prevents timestamp manipulation.
For webhook verification, implement URL allowlisting and use internal verification mechanisms:
// Secure webhook verification
import (
"net/url"
"regexp"
)
type WebhookVerifier struct {
allowedDomains []*regexp.Regexp
secretKey string
}
func NewWebhookVerifier() *WebhookVerifier {
return &WebhookVerifier{
allowedDomains: []*regexp.Regexp{
regexp.MustCompile(`^api\.example\.com$`),
regexp.MustCompile(`^webhooks\.example\.org$`),
},
secretKey: "your-secret-key",
}
}
func (v *WebhookVerifier) isAllowedDomain(rawURL string) bool {
parsed, err := url.Parse(rawURL)
if err != nil {
return false
}
for _, pattern := range v.allowedDomains {
if pattern.MatchString(parsed.Host) {
return true
}
}
return false
}
func (v *WebhookVerifier) verifyWebhook(payload []byte, signature string, rawURL string) bool {
// Validate URL domain before any processing
if !v.isAllowedDomain(rawURL) {
return false
}
// Compute HMAC signature
expected := computeHMAC(payload, v.secretKey)
// Constant-time comparison
return subtle.ConstantTimeCompare([]byte(signature), []byte(expected)) == 1
}
This implementation validates the webhook URL domain against an allowlist before any processing, preventing SSRF through malicious URL injection. The internal verification mechanism eliminates the need to fetch content from external URLs.
For comprehensive protection, implement network-level controls to block outbound requests to internal networks and cloud metadata endpoints. Configure firewalls or service meshes to restrict the HMAC verification service's network access to only necessary external services.
Related CWEs: ssrf
| CWE ID | Name | Severity |
|---|---|---|
| CWE-918 | Server-Side Request Forgery (SSRF) | CRITICAL |
| CWE-441 | Unintended Proxy or Intermediary (Confused Deputy) | HIGH |