Crlf Injection in Rails with Basic Auth
Crlf Injection in Rails with Basic Auth — how this specific combination creates or exposes the vulnerability
Crlf Injection occurs when an attacker can inject a carriage return (CR, \r) and line feed (LF, \n) sequence into a header or status-line context, causing the header to be split and additional headers or response content to be injected. In Rails, this risk is amplified when Basic Authentication is used because the framework parses the Authorization header and then constructs its own status-line and headers. If user-controlled input (such as a username or password) is reflected in headers or logs, and those values contain \r\n sequences, an attacker can inject crafted headers like X-Infected: injected or even split the status line to smuggle a second response.
Consider a scenario where an application logs the Authorization header or echoes a username into a custom header for debugging. A value like admin%0d%0aX-Injected: true (URL-encoded as admin%0D%0A or admin\r\n in raw form) will be decoded by Rails before authentication. When the application later builds a response, the injected CRLF can cause the server to treat X-Injected: true as a separate header, potentially bypassing header-based security assumptions or enabling response splitting in downstream proxies.
Specific to Basic Auth flow: the browser sends an Authorization header like Authorization: Basic base64(username:password). Rails decodes this and may place the credentials into the request environment (e.g., request.env['HTTP_AUTHORIZATION']). If the application uses these values to set custom headers, or if logs include the raw Authorization header, CRLF sequences in the username or password can corrupt the header structure. Even without direct header reflection, log injection via CRLF can facilitate log forging or poisoning, complicating audit trails and security monitoring.
Because Basic Auth transmits credentials on every request, any leakage or manipulation caused by CRLF Injection can persist across multiple calls. While Rails does not automatically reflect user credentials into headers, developers sometimes build custom authentication flows or instrumentation that do. In such cases, failing to sanitize or encode newlines in username/password fields creates a path for injecting status-line splits or additional headers, undermining the integrity of the response.
Basic Auth-Specific Remediation in Rails — concrete code fixes
To mitigate CRLF Injection in Rails with Basic Auth, ensure that any user-controlled data reflected in headers, logs, or status lines is sanitized and that newlines are either rejected or percent-encoded. Below are concrete, safe patterns for handling Basic Auth credentials in Rails controllers and logging.
1. Reject or encode newlines in credentials
Validate usernames and passwords before using them in headers or logs. Reject values containing \r or \n, or encode them to prevent injection.
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :validate_no_crlf_in_auth
private
def validate_no_crlf_in_auth
if request.env['HTTP_AUTHORIZATION']&.include?("\r") || request.env['HTTP_AUTHORIZATION']&.include?("\n")
render plain: 'Bad Request', status: :bad_request
end
end
end
2. Use Rack::Utils to build safe headers
When you must pass credentials into custom headers, use Rack utilities to escape or filter newlines. Avoid string interpolation for header values derived from user input.
# Safe header construction
username = 'admin' # from decoded Basic Auth
custom_value = Rack::Utils.escape_header(username)
response.headers['X-User-Safe'] = custom_value
3. Avoid echoing raw Authorization headers in logs
Configure Rails to filter sensitive parameters and avoid logging raw Authorization headers. Use Rails built-in filtering to prevent CRLF in logs from being injected into log-processing systems.
# config/initializers/filter_parameter_logging.rb
Rails.application.config.filter_parameters += [:authorization, :password]
# In your logger formatter or around_action, avoid including request.env['HTTP_AUTHORIZATION']
# Prefer logging a masked user identifier instead:
def safe_user_identifier
return unless request.authorization
"user=#{request.authorization.split.last[0..3]}***" # masked basic token
end
4. Enforce strict header policies in responses
Set Content-Security-Policy and other security headers via Rails configuration rather than building them from user input. This prevents injected headers from affecting browser behavior.
# config/initializers/secure_headers.rb
Rails.application.config.content_security_policy do |policy|
policy.default_src :self
end
# Ensure no user-controlled values are used in header generation
Rails.application.config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN',
'X-Content-Type-Options' => 'nosniff',
'Referrer-Policy' => 'strict-origin-when-cross-origin'
}
5. Prefer token-based authentication over Basic Auth where possible
Basic Auth embeds credentials in every request and increases exposure surface. Consider using token-based schemes (e.g., Bearer tokens) and keep credentials out of headers that may be reflected. If you must use Basic Auth, treat the decoded username/password as untrusted input and never forward them verbatim to downstream services or logs.