Dns Rebinding in Grape with Hmac Signatures
Dns Rebinding in Grape with Hmac Signatures — how this specific combination creates or exposes the vulnerability
DNS Rebinding is a network-based attack where a malicious webpage causes a victim’s browser to resolve a domain name to an internal IP address that is otherwise unreachable from the internet. When an API protected by Hmac Signatures is hosted behind a hostname that can be rebound, the client-side logic that computes and sends the signature may still trust the original hostname while the browser’s request is routed to a different, attacker-controlled IP. This mismatch can allow the attacker to relay signed requests to internal services that do not enforce strict network-level access controls.
In a Grape API, Hmac Signatures are typically computed over a canonical set of request components—method, path, selected headers, and body—using a shared secret. If the API relies on the request’s Host header or an Origin header to validate the signature scope without also validating the resolved destination IP, an attacker can use DNS Rebinding to present a trusted hostname while the request reaches an internal host. Because the signature is valid for the original hostname and the attacker can trigger the signed request from the victim’s browser, the API may process the request as if it originated from an allowed source, bypassing intended network restrictions.
This becomes especially relevant when the Grape API is used by web or mobile clients that compute signatures in JavaScript or a WebView and where the backend does not independently verify the target IP against an allowlist. The API’s authentication boundary is effectively the signature, not the network path, so rebinding the DNS after signature computation does not invalidate the signature but changes the network destination. The API may then interact with internal management interfaces, databases, or other services that were never intended to be exposed to the client-side network, leading to unauthorized actions or data access.
middleBrick’s 12 security checks include Input Validation and Unsafe Consumption, which can surface exposed internal endpoints and risky header usage that may amplify such scenarios. While middleBrick does not fix the issue, its findings include prioritized guidance to help you address the root cause.
Hmac Signatures-Specific Remediation in Grape — concrete code fixes
To mitigate DNS Rebinding when using Hmac Signatures in Grape, ensure the signature scope explicitly binds to the expected destination IP or a strict hostname-to-IP mapping, and avoid relying solely on mutable headers like Host or Origin for authorization decisions. Below are concrete, syntactically correct examples for a Grape API that implement Hmac Signatures with protections aligned to this guidance.
Example 1: Basic Hmac Signature verification with explicit host validation
# Gemfile
gem 'grape'
gem 'openssl'
# app/api/base_api.rb
class BaseApi < Grape::API
helpers do
def verify_hmac_signature
provided_signature = request.headers['X-API-Signature']
timestamp = request.headers['X-API-Timestamp']
api_key = request.headers['X-API-Key']
# Reject requests with missing headers
error!('Missing authentication headers', 401) if provided_signature.nil? || timestamp.nil? || api_key.nil?
# Optional: reject excessively old requests to mitigate replay
max_age = 300 # seconds
now = Time.now.to_i
error!('Request expired', 401) if (now - timestamp.to_i).abs > max_age
# The canonical string must match what the client signed
method = request.request_method
path = request.path
body = request.body.read
# Ensure body is reset for downstream use
request.body.rewind
# Rebuild the payload exactly as the client did
canonical = "#{method}\n#{path}\n#{timestamp}\n#{body}"
# Fetch the secret for this API key securely (e.g., from a vault or encrypted store)
secret = fetch_secret_for_key(api_key)
# Compute HMAC-SHA256
digest = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), secret, canonical)
computed_signature = Base64.strict_encode64(digest)
# Constant-time comparison to avoid timing attacks
unless ActiveSupport::SecurityUtils.secure_compare(provided_signature, computed_signature)
error!('Invalid signature', 401)
end
# Additional binding: ensure the request resolved to the expected host
# This prevents DNS Rebinding where the Host header is trusted but the IP is not
allowed_hosts = ['api.example.com', 'staging-api.example.com']
unless allowed_hosts.include?(request.host)
error!('Host not allowed', 403)
end
# Optionally validate the resolved IP against an allowlist for sensitive endpoints
allowed_ips = ['192.0.2.10', '198.51.100.20'] # example RFC 5737 addresses
unless allowed_ips.include?(request.ip)
error!('Destination IP not authorized', 403)
end
end
def fetch_secret_for_key(api_key)
# Implement secure secret retrieval; this is a placeholder
# In production, use a vault or encrypted database lookup
secrets = {
'test_key_123' => 'super-secret-shared-secret'
}
secrets.fetch(api_key, nil) || raise('Invalid API key')
end
end
before { verify_hmac_signature }
resource :data do
get do
{ status: 'ok' }
end
end
end
Example 2: Client-side signing helper to ensure consistent canonical form
# Client-side signing (e.g., in JavaScript or Ruby) — keep this in sync with the server
require 'openssl'
require 'base64'
def build_hmac_signature(method, path, timestamp, body, secret)
canonical = "#{method}\n#{path}\n#{timestamp}\n#{body}"
digest = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), secret, canonical)
Base64.strict_encode64(digest)
end
# Example usage:
method = 'GET'
path = '/api/v1/data'
timestamp = Time.now.to_i.to_s
body = ''
secret = 'super-secret-shared-secret'
signature = build_hmac_signature(method, path, timestamp, body, secret)
# Then send these headers:
# X-API-Key: test_key_123
# X-API-Timestamp: #{timestamp}
# X-API-Signature: #{signature}
Key remediation steps reflected in the code:
- Bind the signature to a strict set of allowed hosts and validate request.host in addition to the signature.
- Optionally validate the resolved request.ip against an allowlist for sensitive endpoints, preventing requests that reach internal IPs even if the DNS is rebinded.
- Use a constant-time comparison to avoid timing attacks on the signature.
- Include a timestamp and reject requests outside an acceptable time window to reduce replay risk.
- Ensure the canonical string used for verification matches exactly between client and server (method, path, timestamp, body).
These changes do not rely on the Host header alone for authorization and add network-level checks that reduce the impact of DNS Rebinding. Combine these practices with infrastructure controls such as firewall rules and private networking for defense in depth.