Crlf Injection in Grape with Mutual Tls
Crlf Injection in Grape with Mutual Tls
Crlf Injection occurs when an attacker can insert CRLF sequences (%0D%0A or \r\n) into HTTP headers, causing header splitting and injection. In Grape, this typically manifests through user-controlled inputs that flow into response header construction, such as custom header values, location redirects, or via query/path parameters that influence header logic.
When Mutual Transport Layer Security (Mutual TLS) is enforced, the server requests a client certificate during the TLS handshake. This additional authentication layer can create a false sense of security. Because Mutual TLS secures the transport and authenticates the client, developers may assume all inputs are safe and skip proper validation/sanitization. However, Crlf Injection is an application-layer issue; it is not mitigated by transport security. A token-authenticated request (validated via client cert) can still carry malicious header content if the application directly concatenates user data into headers. Therefore, the combination of Crlf Injection and Mutual TLS can expose applications where trust in the client leads to relaxed input validation, increasing the risk of response splitting, cache poisoning, or session fixation via injected headers.
For example, consider a Grape API that uses a query parameter to set a custom header without sanitization:
class V1 < Grape::API
format :json
desc 'Returns a greeting with a custom header'
params do
requires :name, type: String
optional :extra_header, type: String, desc: 'Custom header value'
end
get :greet do
tt
header 'X-Greeting', "Hello #{params[:name]}"
header 'X-Extra', params[:extra_header] if params[:extra_header]
{ message: "Hi #{params[:name]}" }
end
end
end
An attacker can supply extra_header as foo\r\nSet-Cookie: session=hijacked. Without sanitization, this injects a new header, potentially overriding or adding headers. Even with Mutual TLS ensuring the client is authenticated, the server must treat all header inputs as untrusted.
Another scenario involves redirects. If a Grape endpoint uses user input to construct a redirect location, CRLF injection can lead to response splitting and open redirects:
get '/redirect' do
url = params[:url]
redirect url
end
An input like https://example.com\r\nContent-Length: 0\r\n\r\nHTTP/1.1 200 OK can split the response and inject arbitrary content. Mutual TLS does not prevent this; the application must canonicalize and validate the URL, ensuring it does not contain line breaks and is an absolute URL to a trusted host.
In summary, Mutual TLS provides channel authentication but does not protect against application-level input mishandling. Crlf Injection in Grape with Mutual TLS is a failure to sanitize data that enters the HTTP response layer, regardless of how strongly the client is authenticated.
Mutual Tls-Specific Remediation in Grape
Remediation focuses on strict input validation and output encoding for any data that influences HTTP headers. Do not rely on Mutual TLS to filter malicious input. Apply canonicalization and deny-list or allow-list approaches for header values.
1. Validate and sanitize header inputs. Reject or encode CRLF characters in any user-controlled data used in headers. For string parameters intended for headers, strip or encode \r and \n (and their URL-encoded forms). A simple approach is to treat header values as opaque strings and reject any that contain line breaks:
def safe_header_value(value)
return nil if value.to_s.include?("\r") || value.to_s.include?("\n")
value
end
class V1 < Grape::API
format :json
helpers do
def set_safe_header(name, value)
sanitized = safe_header_value(value)
header(name, sanitized) if sanitized
end
end
get :greet do
require 'securerandom'
set_safe_header('X-Request-ID', params[:request_id])
set_safe_header('X-Greeting', "Hello #{params[:name]}")
{ message: "Hi #{params[:name]}" }
end
end
This ensures that any carriage return or line feed is not passed to the header, preventing injection regardless of Mutual TLS status.
2. Validate redirect URLs strictly. For endpoints that redirect, resolve the URL to ensure it is absolute, uses an expected scheme (https), and does not contain newline characters. Avoid using user input directly in redirect. Instead, map to a known set of allowed paths or use a whitelist of domains:
ALLOWED_HOSTS = ['api.example.com', 'cdn.example.com']
def safe_redirect_url(user_url)
uri = URI.parse(user_url)
raise Grape::Exceptions::Validation, 'Invalid URL' unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
raise Grape::Exceptions::Validation, 'Disallowed host' unless ALLOWED_HOSTS.include?(uri.host)
# Ensure no newline characters are present
raise Grape::Exceptions::Validation, 'Invalid URL' if user_url.include?("\n") || user_url.include?("\r")
user_url
rescue URI::InvalidURIError
raise Grape::Exceptions::Validation, 'Invalid URL'
end
class V1 < Grape::API
get '/redirect' do
url = safe_redirect_url(params[:url])
redirect url, 302
end
end
Mutual TLS helps authenticate clients, but the application must still enforce strict URL validation to prevent response splitting.
3. Use framework-level header utilities. Where possible, rely on Grape's built-in header handling that avoids direct string interpolation. For dynamic values, ensure they are simple strings without control characters. Avoid constructing header strings manually.
4. Complement with Mutual TLS configuration. While Mutual TLS does not stop Crlf Injection, it remains valuable for transport security. Configure your server to require client certificates and validate them properly. Example Puma/Rack configuration snippet (not a security fix for Crlf but for overall TLS posture):
# In config/puma.rb or equivalent
ssl_bind '0.0.0.0', '8443', {
cert: '/path/to/server.crt',
key: '/path/to/server.key',
verify_mode: 'verify_peer',
ca_file: '/path/to/ca_bundle.pem'
}
Combine this with the header sanitization helpers to ensure both transport and application-layer integrity.