Crlf Injection in Sinatra with Hmac Signatures
Crlf Injection in Sinatra with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Crlf Injection occurs when an attacker can inject CRLF characters (\r\n) into a header or cookie value, causing the application to split headers and inject additional headers or set arbitrary cookies. In Sinatra, this risk is pronounced when Hmac Signatures are used to validate the integrity of requests but the signature is computed over a subset of headers while the application still processes attacker-controlled header values.
Consider a Sinatra service that expects an Hmac-Signature header. The server computes the signature over selected headers and a timestamp, then compares it to the value sent by the client. If the application reads headers such as X-Forwarded-Host, X-Forwarded-Proto, or a custom X-Original-URI without strict validation, and then includes those values in the signature base string, an attacker can inject a newline and append a new header like X-Injected: true or set a cookie.
For example, a crafted request might include:
X-Original-URI: /api/data\r\nX-Injected: injected\r\n
If the Sinatra app concatenates the raw header value into the signed payload, the Hmac Signature may still verify (because the attacker does not know the secret), but the server-side logic may have already parsed the injected header before signature verification completes or may treat the injected header as authoritative. This can lead to header smuggling, cache poisoning, or session fixation depending on how downstream components interpret the duplicated or split headers. The vulnerability is not in the Hmac algorithm itself, but in how the application normalizes, selects, and uses header values before signing and after verification.
Insecure patterns include using headers directly in redirects, constructing URLs from X-Forwarded headers without strict allowlisting, or setting cookies based on unchecked inputs. Even when the signature covers a timestamp and a subset of headers, failing to reject or sanitize embedded newlines means the effective request line or header block seen by Sinatra can be altered, bypassing intended scope of the signature.
Hmac Signatures-Specific Remediation in Sinatra — concrete code fixes
Remediation focuses on strict header normalization, deterministic signature base construction, and rejecting any input containing CRLF sequences. Below is a secure Sinatra example that computes and verifies an Hmac Signature while preventing Crlf Injection.
require 'sinatra'
require 'openssl'
require 'base64'
require 'json'
require 'time'
# Configuration (use ENV in production)
SECRET = ENV.fetch('HMAC_SECRET') { 'development_secret_change_me' }
ALLOWED_HEADERS = ['x-request-id', 'content-type', 'x-timestamp'].freeze
def sanitize_header_value(value)
# Reject or strip CRLF to prevent header injection
if value.to_s.include?("\r") || value.to_s.include?("\n")
raise ArgumentError, 'Header value contains forbidden newline characters'
end
value.to_s.strip
end
def build_signature_base(headers, timestamp)
# Canonicalize and include only allowed headers
canonical = ALLOWED_HEADERS.sort.map do |name|
value = sanitize_header_value(headers[name])
"#{name.downcase}:#{value}"
end.join("\n")
"#{timestamp}\n#{canonical}"
end
def compute_hmac(message)
OpenSSL::HMAC.hexdigest('SHA256', SECRET, message)
end
before do
# Ensure required headers are present and safe
halt 400, 'Missing X-Timestamp' unless request.env['HTTP_X_TIMESTAMP']
halt 400, 'Invalid timestamp' unless request.env['HTTP_X_TIMESTAMP'] =~ /^\d{10}$/
halt 400, 'Missing X-Signature' unless request.env['HTTP_X_SIGNATURE']
timestamp = request.env['HTTP_X_TIMESTAMP']
headers_for_sig = {
'x-request-id' => request.env['HTTP_X_REQUEST_ID'] || '',
'content-type' => request.env['CONTENT_TYPE'] || '',
'x-timestamp' => timestamp
}
base = build_signature_base(headers_for_sig, timestamp)
expected = compute_hmac(base)
provided = request.env['HTTP_X_SIGNATURE']
halt 401, 'Invalid signature' unless Rack::Utils.secure_compare(expected, provided)
# Safe to use headers; CRLF already rejected
@x_request_id = headers_for_sig['x-request-id']
end
get '/api/data' do
content_type :json
{ status: 'ok', request_id: @x_request_id }.to_json
end
Key points in this implementation:
sanitize_header_valueexplicitly rejects values containing carriage return or line feed characters before they are used in the signature base or stored.- Only a strict allowlist of headers (
ALLOWED_HEADERS) is included in the signature computation, preventing attacker-controlled headers from influencing the signed payload. - The timestamp is validated with a simple regex to ensure it is a numeric epoch, mitigating replay risks and ensuring deterministic base construction.
- Use of
Rack::Utils.secure_compareprevents timing attacks during signature verification.
In production, further hardening includes enforcing HTTPS via a strict before block that checks TLS, setting secure and http_only flags on any cookies, and avoiding the use of raw user-supplied header values in redirects or URL construction. These measures ensure that Hmac Signatures remain effective and that Crlf Injection cannot be leveraged against the Sinatra application.