Crlf Injection in Axum with Cockroachdb
Crlf Injection in Axum with Cockroachdb — how this specific combination creates or exposes the vulnerability
Crlf Injection occurs when an attacker can inject a CRLF sequence (\r\n) into a header or a structured data field that later influences how a downstream component interprets message boundaries. In an Axum application that uses Cockroachdb as the backend, the risk typically arises when user-controlled input is reflected into HTTP response headers, SQL identifiers, or query-driven content without proper sanitization.
Consider an endpoint that builds a redirect or a custom header using a value taken directly from a Cockroachdb row. If the stored value contains \r\n and the application reuses it in an HTTP context, an attacker can inject additional headers or split the response, leading to response splitting, cache poisoning, or cross-site scripting via injected headers. Even when Cockroachdb itself does not introduce the injection, the combination of Axum’s routing and Cockroachdb’s flexible schema can amplify impact if input validation is weak.
In practice, an Axum handler might read a user’s profile display name from Cockroachdb and place it into a header such as X-Display-Name. If the display name is attacker-controlled and contains \r\n, and Axum serializes the header without validation, the injected sequence can create a new header line. This can enable HTTP response splitting or header injection. Because Cockroachdb often stores free-form user data, developers must treat any field that may be reflected into HTTP messages as untrusted, even when the data originates from a strongly typed ORM layer.
Another scenario involves query-driven content generation where Axum uses Cockroachdb rows to construct messages or filenames. If a filename derived from a database column is used in a Content-Disposition header without sanitization, injected CRLF can lead to header injection or path traversal when the file is served. The database does not need to be vulnerable; the risk is in how Axum uses data retrieved from Cockroachdb in network-facing contexts.
Because Axum is a typed web framework, developers may assume that type safety prevents injection. However, type safety does not sanitize output. Any data retrieved from Cockroachdb that reaches headers, redirects, or structured logs must be validated and encoded for the target context. MiddleBrick’s scans detect such issues by correlating OpenAPI specifications with runtime behavior, identifying endpoints where database-derived data reaches network boundaries without sanitization.
Cockroachdb-Specific Remediation in Axum — concrete code fixes
Remediation focuses on context-aware encoding and strict validation before using any Cockroachdb-derived data in HTTP messages. Below are concrete Axum examples that demonstrate safe handling.
1. Validate and sanitize header values from Cockroachdb
use axum::headers::HeaderMapExt;
use axum::response::Response;
use axum::http::HeaderName;
fn sanitize_header_value(value: &str) -> Option {
// Reject CRLF to prevent response splitting
if value.contains("\r") || value.contains("\n") {
return None;
}
Some(value.trim().to_string())
}
async fn user_profile_handler(
Extension(pool): Extension,
Path(user_id): Path,
) -> Result {
let conn = pool.get().map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
let display_name: String = sqlx::query_scalar("SELECT display_name FROM users WHERE id = $1")
.bind(user_id)
.fetch_one(&conn)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
let display_name = sanitize_header_value(&display_name)
.ok_or_else(|| (StatusCode::BAD_REQUEST, "Invalid display name".to_string()))?;
let mut response = Response::new("OK".into());
response.headers_mut().insert(
HeaderName::from_static("x-display-name"),
display_name.parse().map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Invalid header".to_string()))?,
);
Ok(response)
}
This pattern ensures that any data retrieved from Cockroachdb is checked for CRLF before being placed into an HTTP header. Rejecting or sanitizing the value prevents response splitting and keeps the header structure intact.
2. Use parameterized queries and avoid dynamic identifiers
async fn get_user_safe(
pool: &Pool,
user_id: i32,
) -> Result {
// Always use parameterized queries; never interpolate values into SQL text
let user = sqlx::query_as!(
User,
"SELECT id, email, display_name FROM users WHERE id = $1",
user_id
)
.fetch_one(pool)
.await?;
Ok(user)
}
Parameterized queries prevent SQL injection and avoid the need to escape identifiers. If you must use dynamic identifiers (e.g., table or column names), use a strict allowlist rather than string concatenation with user input.
3. Encode data for the target context before use in redirects or filenames
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
fn safe_filename_component(value: &str) -> String {
// Percent-encode non-alphanumeric characters to avoid CRLF and path traversal
utf8_percent_encode(value, NON_ALPHANUMERIC).to_string()
}
async fn export_handler(
Extension(pool): Extension,
Path(user_id): Path,
) -> Result, (StatusCode, String)> {
let conn = pool.get().map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
username: String = sqlx::query_scalar("SELECT username FROM users WHERE id = $1")
.bind(user_id)
.fetch_one(&conn)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
let filename = format!("profile-{}.txt", safe_filename_component(&username));
let body = format!("User data for {}", username);
let mut response = Response::new(Body::from(body));
response.headers_mut().insert(
"Content-Disposition",
format!(r#"attachment; filename="{}""#, filename).parse().map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Invalid header".to_string()))?,
);
Ok(response)
}
Percent-encoding the username before embedding it into a filename and Content-Disposition header prevents CRLF injection and path traversal. This pattern is particularly important when database values influence HTTP messages.
4. Apply allowlists and schema constraints in Cockroachdb
Where possible, constrain data at the database level. For example, enforce display names to alphanumeric characters and limited length via CHECK constraints. This reduces the likelihood of malicious data being stored and later reflected into HTTP contexts.
-- Example constraint to reduce injection surface
ALTER TABLE users ADD CONSTRAINT valid_display_name CHECK (display_name ~ '^[A-Za-z0-9 _-]{1,100}$');
Combined with application-level validation, this defense-in-depth approach minimizes the impact of any oversight in Axum handlers that use Cockroachdb data.