Crlf Injection in Rails with Jwt Tokens
Crlf Injection in Rails with Jwt Tokens — 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 (\n) sequence into a header or cookie value. In a Ruby on Rails application that uses JWT tokens for authentication, this becomes particularly dangerous because the application may embed user-controlled data into JWT claims or directly into HTTP headers that influence how tokens are processed.
Consider a scenario where a Rails controller builds a JWT using data from request parameters without proper sanitization:
payload = { sub: params[:user_id], email: params[:email] }
token = JWT.encode(payload, Rails.application.secrets.secret_key_base, 'HS256')
If params[:email] contains a sequence like example.com\r\nSet-Cookie: session=attacker, and the token is later used in a header or logged in a way that this value is reflected, the injected CRLF can split the header. This can lead to HTTP response splitting or the injection of additional cookies and headers when the token is decoded and used downstream.
Another common pattern involves passing the JWT in an Authorization header as a Bearer token while also setting custom headers derived from user input:
request.headers['X-User-Token'] = token
If the token or its claims include unsanitized newlines, and the application later reuses these values in header construction, the injected CRLF can cause the parser to treat subsequent text as a new header. This can expose internal logic or enable cache poisoning. Even when JWTs are validated using libraries like jwt, the risk emerges if decoded claims are used directly in headers, cookies, or logs without escaping or validation.
Rails' default behavior of parsing incoming headers and cookies can be leveraged by an attacker to chain CRLF injection with JWT handling. For example, if a decoded JWT claim containing a newline is placed into a Set-Cookie header via server-side logic, an attacker can inject a new cookie or even a new HTTP header, bypassing intended access controls or stealing session identifiers.
Logging mechanisms that output JWT claims or headers are also at risk. If a newline in a JWT email claim causes log entries to be split, an attacker can obfuscate audit trails or trigger log injection issues that complicate monitoring and incident response.
Because JWT tokens often carry sensitive authorization information, the impact of CRLF injection in this context extends beyond simple header manipulation. It can lead to session fixation, unauthorized access, or information disclosure when combined with other weaknesses. The key factor is the uncontrolled use of data that originates from the token or its surrounding request context.
Jwt Tokens-Specific Remediation in Rails — concrete code fixes
To mitigate CRLF injection risks when working with JWT tokens in Rails, you must sanitize and validate all data that can reach headers, cookies, or logs. The following patterns demonstrate secure handling of JWT-related values.
First, avoid directly embedding untrusted data into JWT claims that may later be used in header construction. If you must include user input, enforce strict character constraints and strip or encode CRLF sequences:
email = params[:email].gsub(/[\r\n]/, '')
payload = { sub: params[:user_id], email: email }
token = JWT.encode(payload, Rails.application.secrets.secret_key_base, 'HS256')
This ensures that newline characters cannot be used to split headers or cookies when the claim is later processed. For additional safety, validate email format with a strict regex before encoding the token.
When setting cookies from JWT claims, always use Rails’ built-in cookie mechanisms and avoid manual header assembly. Rails automatically handles proper formatting and prevents CRLF injection in standard cookie setting:
cookies.permanent[:token] = { value: token, httponly: true, secure: Rails.env.production? }
If you need to set custom headers based on token data, sanitize the values explicitly:
safe_token = token.gsub(/[\r\n]/, '')
request.headers['X-Safe-Token'] = safe_token
This removes any remaining newline sequences before the value is assigned to a header. In logging, filter JWT claims to remove or replace problematic characters:
Rails.logger.info "Token issued for #{payload[:sub].to_s.gsub(/[\r\n]/, '')}"
For applications that decode incoming JWTs and use claims in multiple contexts, centralize sanitization in a service object to ensure consistent treatment across controllers and background jobs. Combine this with strong parameter filtering and content security policies to reduce the attack surface associated with CRLF injection.