HIGH crlf injectionactixbearer tokens

Crlf Injection in Actix with Bearer Tokens

Crlf Injection in Actix with Bearer Tokens — how this specific combination creates or exposes the vulnerability

Crlf Injection occurs when user-controlled data is placed into HTTP headers without proper sanitization, allowing an attacker to inject additional header lines via carriage return (CR, \r) and line feed (LF, \n) sequences. In Actix web applications, if a developer constructs response headers using unsanitized input—such as a Bearer token extracted from an Authorization header or a custom header—this can lead to response splitting or header injection. For example, if an API endpoint echoes a token or a token-derived value into a header like X-Token-Info, an attacker-provided token containing \r\n can inject extra headers or even split the response body, enabling HTTP response splitting, cache poisoning, or cross-site scripting via injected headers.

When Bearer tokens are involved, the risk is often indirect: the token itself may originate from an untrusted source (e.g., a client-supplied header or a query parameter) and then be reflected into another header. Consider an Actix handler that reads a token from a custom header and forwards it into a downstream service response header without validation:

use actix_web::{web, HttpResponse, Responder};

async fn reflect_token(headers: actix_web::HttpRequest) -> impl Responder {
    if let Some(token) = headers.headers().get("X-Custom-Token") {
        let token_str = token.to_str().unwrap_or("");
        HttpResponse::Ok()
            .insert_header(("X-Token-Echo", token_str))
            .body(format!("Token echoed: {token_str}"))
    } else {
        HttpResponse::BadRequest().body("Missing token")
    }
}

If X-Custom-Token contains injected\r\nSet-Cookie: session=attacker, the response will include an additional Set-Cookie header, potentially overriding session management. Even when using standard Authorization Bearer headers, improper validation before logging, debugging output, or inclusion in custom headers can expose injection surfaces. Actix does not automatically sanitize header values; developers must treat any header-derived data as untrusted. This is especially important when tokens contain structured or opaque strings that may include newline characters in edge cases (e.g., base64url-encoded data with line breaks, or tokens generated by poorly configured libraries).

The combination of Actix's flexible header handling and Bearer token reflection creates a scenario where an attacker can manipulate the response protocol itself. Successful injection can lead to cross-protocol attacks, such as injecting a new request in a keep-alive pipeline (HTTP request smuggling) when the application acts as a proxy, or bypassing intended security controls by injecting headers like Content-Security-Policy or X-Frame-Options. Because Bearer tokens are often treated as opaque, developers may overlook the need to validate or sanitize them before using them in header construction, inadvertently exposing a Crlf Injection vector.

Bearer Tokens-Specific Remediation in Actix — concrete code fixes

Remediation focuses on strict validation and safe handling of any data that may originate from Bearer tokens or other header values before they are used in response headers. The safest approach is to avoid reflecting untrusted input into headers entirely. If reflection is necessary, sanitize by removing or encoding CR and LF characters and enforce strict token format expectations.

Below are concrete, safe patterns for Actix handlers that work with Bearer tokens.

1. Reject or sanitize newline characters in token-derived header values

Ensure that any string derived from a token (or any other user input) does not contain \r or \n before inserting it into a header. Use explicit filtering or rejection:

use actix_web::{web, HttpResponse, Responder};

fn sanitize_header_value(value: &str) -> Option<String> {
    if value.contains('\r') || value.contains('\n') {
        return None;
    }
    Some(value.to_string())
}

async fn safe_token_endpoint(headers: actix_web::HttpRequest) -> impl Responder {
    if let Some(token_header) = headers.headers().get("Authorization") {
        if let Ok(token_bytes) = token_header.to_str() {
            // Expect Bearer scheme; extract token part only
            let token = if let Some(stripped) = token_bytes.strip_prefix("Bearer ") {
                stripped.trim()
            } else {
                ""
            };

            if let Some(safe_token) = sanitize_header_value(token) {
                HttpResponse::Ok()
                    .insert_header(("X-Token-Hash", &safe_token[..8])) // safe partial echo
                    .body(format!("Token accepted, length: {}", safe_token.len()))
            } else {
                HttpResponse::BadRequest().body("Token contains invalid characters")
            }
        } else {
            HttpResponse::BadRequest().body("Invalid header encoding")
        }
    } else {
        HttpResponse::Unauthorized().body("Missing Authorization header")
    }
}

This example extracts the token portion after Bearer, checks for newline characters, and rejects the request if unsafe characters are present. It also demonstrates minimal reflection—only a safe, truncated hash of the token is echoed, not the full token.

2. Avoid reflecting tokens into Set-Cookie or other sensitive headers

Never use Bearer token values directly in Set-Cookie or other header fields that influence session handling. If you must associate a token with a cookie, generate a server-side mapping (e.g., a short-lived session ID) and set the cookie with secure, predefined attributes:

use actix_web::{web, HttpResponse, Responder};
use uuid::Uuid;

async fn login_handler(headers: actix_web::HttpRequest) -> impl Responder {
    if let Some(auth) = headers.headers().get("Authorization") {
        if let Ok(auth_str) = auth.to_str() {
            if auth_str.starts_with("Bearer ") {
                let _token = auth_str.trim().strip_prefix("Bearer ").unwrap_or("");
                // Do not echo token into headers; create a server-side session
                let session_id = Uuid::new_v4().to_string();
                HttpResponse::Ok()
                    .insert_header(("Set-Cookie", format!("session_id={}; Path=/; HttpOnly; Secure", session_id).as_str()))
                    .body("Authenticated")
            } else {
                HttpResponse::BadRequest().body("Invalid Authorization format")
            }
        } else {
            HttpResponse::BadRequest().body("Invalid header encoding")
        }
    } else {
        HttpResponse::Unauthorized().body("Missing Authorization header")
    }
}

In this pattern, the Bearer token is validated for format but never echoed into a header. Instead, a cryptographically random session identifier is used in a Set-Cookie header with secure defaults (HttpOnly, Secure), mitigating the risk of CRLF Injection via token reflection.

These practices align with secure header handling principles and reduce the attack surface when Bearer tokens are part of the request processing flow.

Frequently Asked Questions

Can an attacker exploit CRLF Injection if the Bearer token is validated for format only?
Yes. Format validation (e.g., checking Bearer prefix) does not prevent CRLF Injection if the token or any derived value is later concatenated into headers without removing \r or \n. Always sanitize or avoid reflecting token-derived strings into headers.
Does using the Authorization header with Bearer tokens prevent CRLF Injection by itself?
No. The Authorization header is parsed by the server before reaching application code, but if your application extracts the token and reuses it in other response headers without sanitization, CRLF Injection remains possible.