Crlf Injection in Sinatra with Firestore
Crlf Injection in Sinatra with Firestore — how this specific combination creates or exposes the vulnerability
Crlf Injection occurs when an attacker can inject carriage return (CR, \r) and line feed (\n) characters into an HTTP header or a downstream system that interprets these sequences as line breaks. In Sinatra, this typically arises when user-controlled data is directly interpolated into headers, response lines, or logs without proper sanitization. When a Sinatra application uses Firestore as a backend data store, the risk surface expands because Firestore documents or IDs can themselves contain attacker-influenced values that later flow into HTTP responses or logs.
Consider a Sinatra route that retrieves a Firestore document based on a client-supplied identifier and returns a field in a custom header or JSON response:
require 'sinatra'
require 'google/cloud/firestore'
firestore = Google::Cloud::Firestore.new
get '/user/:uid' do
user_doc = firestore.doc("users/#{params[:uid]}").get
if user_doc.exists?
user_data = user_doc.data
# Vulnerable: user-controlled data used in a header without sanitization
headers['X-User-Name'] = user_data['name']
{ name: user_data['name'], email: user_data['email'] }.to_json
else
status 404
{ error: 'not_found' }.to_json
end
end
If the Firestore document’s name field contains a value like John\r\nX-Injected: true, the X-User-Name header will be split, potentially injecting an additional header X-Injected: true. This can enable header manipulation, HTTP response splitting, or cache poisoning. Additionally, logging the raw Firestore document data without sanitization may allow injected sequences to reach log files, where they might be interpreted by log aggregation tools or viewed in consoles that treat newlines as separators.
The Firestore interaction amplifies the issue when IDs or document fields are not strictly validated. For example, an attacker might provide a document ID containing newline characters to probe visibility of unintended documents or to bypass assumptions in path construction:
get '/profile/:doc_path' do
# Unsafe: doc_path may contain newline-separated segments
doc_ref = firestore.doc(params[:doc_path])
doc = doc_ref.get
# Further processing...
end
If the path includes encoded or raw CR/LF characters, it may lead to accessing documents outside the intended scope or corrupting log formatting. Even though Sinatra does not inherently treat Firestore data as unsafe, the framework’s dynamic routing and header-setting mechanisms can propagate these characters into network-level artifacts, triggering injection effects in proxies, browsers, or intermediate infrastructure.
Firestore-Specific Remediation in Sinatra — concrete code fixes
Remediation focuses on input validation, output encoding, and defensive handling of Firestore data before it reaches headers, responses, or logs. Treat all Firestore field values as untrusted, even if they originate from your own writes.
1. Validate and sanitize Firestore document IDs and fields
Restrict document IDs to a safe character set and reject inputs containing control characters or whitespace. For fields used in headers, strip or encode CR and LF characters.
def safe_header_value(value)
# Remove CR and LF characters to prevent header splitting
value.to_s.gsub(/[\r\n]/, '')
end
get '/user/:uid' do
uid = params[:uid].to_s
unless uid.match?(/^[a-zA-Z0-9\-_]+$/)
status 400
{ error: 'invalid_uid' }.to_json && return
end
user_doc = firestore.doc("users/#{uid}").get
if user_doc.exists?
user_data = user_doc.data
headers['X-User-Name'] = safe_header_value(user_data['name'])
{ name: user_data['name'], email: user_data['email'] }.to_json
else
status 404
{ error: 'not_found' }.to_json
end
end
2. Avoid direct concatenation in Firestore paths
Do not allow user input to directly form document paths. Use whitelisting or mapping to canonical references.
get '/profile/:user_id' do
user_id = params[:user_id].to_s
# Reject invalid IDs early
unless user_id.match?(/^[a-z0-9]{8,32}$/)
status 400
{ error: 'invalid_id' }.to_json && return
end
# Map user_id to a canonical document reference
doc_ref = firestore.doc("profiles/#{user_id}")
doc = doc_ref.get
# Safe to use document data after validation
{ id: doc.id, data: doc.data }.to_json
end
3. Encode data in JSON responses and avoid header injection
Never place raw Firestore fields into HTTP headers. If you must include metadata, use response body fields and encode values appropriately for JSON.
get '/settings/:doc_id' do
doc_id = params[:doc_id].to_s.gsub(/[^a-zA-Z0-9\-_.]/, '')
doc = firestore.doc("settings/#{doc_id}").get
if doc.exists?
# Safe: values remain in JSON body, not headers
{ settings: doc.data.transform_values { |v| Rack::Utils.escape_html(v.to_s) } }.to_json
else
status 404
{ error: 'not_found' }.to_json
end
end
4. Sanitize before logging
If logging Firestore data, remove or replace line breaks to preserve log structure.
def sanitize_log(value)
value.to_s.gsub(/[\r\n]/, ' ').squeeze(' ')
end
# Example within route logic:
logger.info("User profile: #{sanitize_log(user_doc.data.inspect)}")
These measures ensure that Firestore-driven content does not introduce newline-based injection vectors in your Sinatra application, protecting headers, logs, and downstream consumers.