Log Injection in Actix
How Log Injection Manifests in Actix
Log injection in Actix occurs when untrusted user input is written directly to log files without proper sanitization, allowing attackers to manipulate log structure and content. In Actix applications, this typically happens through request parameters, headers, or body content that gets logged by middleware or route handlers.
A common Actix-specific scenario involves the log middleware, which logs HTTP requests by default. When an attacker sends a request with crafted headers or query parameters containing newline characters, they can inject arbitrary log entries. For example, a request with a User-Agent header containing "\nINFO 2023-01-01T00:00:00Z: Attacker accessed admin panel" will create a new log line that appears legitimate to log analyzers.
// Vulnerable Actix route handler
#[get("/search")]
async fn search(query: web::Query<HashMap<String, String>>) -> impl Responder {
info!("Search query: {:?}", query);
HttpResponse::Ok().finish()
}
// Malicious request:
// GET /search?q=test%0AINFO%20This%20is%20a%20fake%20log%20entryThe %0A (URL-encoded newline) breaks the log format, making the injected text appear as a separate log entry. This is particularly dangerous when logs are parsed by security monitoring tools or used for audit trails.
Another Actix-specific vulnerability occurs with structured logging using serde_json or similar crates. When logging request bodies or JSON payloads directly, attackers can craft input that breaks the JSON structure:
// Vulnerable logging
#[post("/api/data")]
async fn api_data(payload: web::Json<MyData>) -> impl Responder {
info!("Received payload: {:?}", payload.0);
// ... processing
}
// Malicious payload:
// {"key": "value"}
// "malicious": true,
// "data": "injected"
Actix's default logging format uses the log crate with env_logger, which doesn't automatically sanitize input. The log macro expands to calls that write directly to stdout/stderr or configured appenders, making injection straightforward if input isn't validated.
Request logging middleware in Actix can also be exploited. The actix-web-middleware-log crate and similar implementations often log headers, query strings, and bodies without sanitization:
// Middleware that logs request details
pub struct RequestLogger;
impl Middleware<ServiceRequest> for RequestLogger {
fn start(&self, req: &ServiceRequest) -> ServiceResult<ServiceResponse> {
// Direct logging of headers without sanitization
info!("Request from {:?}: {:?}", req.peer_addr(), req.headers());
Ok(req.head())
}
}
Attackers can exploit this by sending headers with newline characters or even control characters that manipulate terminal output when logs are viewed directly.
Actix-Specific Detection
Detecting log injection in Actix applications requires both static analysis and runtime monitoring. Static analysis should focus on identifying logging statements that handle user input without sanitization.
Using middleBrick's API security scanner, you can detect log injection vulnerabilities in Actix applications. The scanner examines your running Actix service's endpoints and identifies patterns where user input flows into logging functions. middleBrick's black-box scanning tests for newline injection, JSON structure manipulation, and other log injection techniques specific to Actix's logging patterns.
// middleBrick scan command
middlebrick scan https://your-actix-app.com --output json
// Example finding from middleBrick
{
"category": "Input Validation",
"severity": "medium",
"finding": "Log injection vulnerability in /search endpoint",
"description": "User input from query parameters is logged without sanitization",
"remediation": "Sanitize log input or use structured logging with proper escaping"
}Runtime detection involves monitoring log files for anomalies. In Actix applications, watch for:
- Unexpected timestamp formats or log levels in entries
- Multiple log entries appearing from a single request
- JSON parsing errors in structured logs
- Log entries with suspicious content patterns
- Timing anomalies where log injection might be used for timing attacks
For Actix applications using tracing crate (common in newer Actix projects), detection should include checking for span! and event! macros that might log unvalidated input:
// Vulnerable tracing usage
#[get("/trace")]
async fn trace_endpoint(info: web::Path<String>) -> impl Responder {
let span = tracing::span!(tracing::Level::INFO, "processing", user_input = %info);
let _enter = span.enter();
// ... processing
}
// Malicious request: /trace/any%0AINFO%20Injected%20spanmiddleBrick's continuous monitoring (Pro plan) can be configured to periodically scan your Actix API endpoints, alerting you when new log injection vulnerabilities are detected as your application evolves.
Actix-Specific Remediation
Remediating log injection in Actix requires a multi-layered approach. The most effective strategy combines input sanitization, structured logging, and careful logging configuration.
First, implement input sanitization for any user data that gets logged. Create a utility function to escape dangerous characters:
use actix_web::{web, HttpResponse};
fn sanitize_log_input(input: &str) -> String {
input
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t")
.replace("\"", "\\\"")
}
#[get("/search")]
async fn search(query: web::Query<HashMap<String, String>>) -> impl Responder {
let sanitized_query = query.iter()
.map(|(k, v)| (sanitize_log_input(k), sanitize_log_input(v)))
.collect::<HashMap<_, _>>();
info!("Search query: {:?}", sanitized_query);
HttpResponse::Ok().finish()
}
For structured logging, use tracing crate with JSON output, which automatically handles escaping:
use actix_web::{web, HttpResponse};
use tracing::{info, instrument};
#[instrument(fields(query = ?query.0))]
#[get("/search")]
async fn search(query: web::Query<HashMap<String, String>>) -> impl Responder {
HttpResponse::Ok().finish()
}
// In Cargo.toml:
// tracing = "0.1"
// tracing-subscriber = { version = "0.3", features = ["json"] }
// Configure JSON logging in main()
#[actix_web::main]
async fn main() -> std::io::Result<()> {
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
let fmt_layer = tracing_subscriber::fmt::layer()
.with_target(false)
.with_level(false)
.json()
.with_writer(std::io::stderr);
tracing_subscriber::registry()
.with(fmt_layer)
.init();
HttpServer::new(|| App::new())
.bind(("127.0.0.1", 8080))?;
Ok(())
}
Implement custom logging middleware that validates and sanitizes log entries before they're written:
use actix_web::{dev::Service, dev::ServiceRequest, dev::ServiceResponse, Error};
use futures_util::future::BoxFuture;
use log::{info, LevelFilter};
pub struct SanitizingLogger;
impl actix_web::dev::Transform for SanitizingLogger
where
S: Service,
{
type Response = ServiceResponse;
type Error = Error;
type Transform = SanitizingLoggerMiddleware<S>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(SanitizingLoggerMiddleware { service }))
}
}
pub struct SanitizingLoggerMiddleware<S> {
service: S,
}
impl<S> Service<ServiceRequest> for SanitizingLoggerMiddleware<S>
where
S: Service<ServiceRequest>,
{
type Response = ServiceResponse;
type Error = Error;
type Future = BoxFuture<'static, Result<ServiceResponse, Error>>;
actix_web::dev::forward_ready!(service);
fn call(&mut self, req: ServiceRequest) -> Self::Future {
let headers = req.headers().clone();
let sanitized_headers = headers
.iter()
.map(|(k, v)| (k.clone(), v.to_str().unwrap_or("").to_string()))
.collect::<Vec<_>>();
info!("Request: {} {} Headers: {:?}",
req.method(), req.uri(), sanitized_headers);
Box::pin(self.service.call(req))
}
}
// Use in main()
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.wrap(SanitizingLogger)
.service(search)
})
.bind(("127.0.0.1", 8080))?;
Ok(())
}
For Actix applications that log request bodies or JSON payloads, implement content filtering:
use actix_web::{web, HttpResponse, HttpRequest};
use serde_json::Value;
fn safe_log_json(value: &Value) -> String {
serde_json::to_string(value)
.unwrap_or_else(|_| "{}".to_string())
.replace('\n', "\\n")
.replace('\r', "\\r")
}
#[post("/api/data")]
async fn api_data(payload: web::Json<Value>) -> impl Responder {
let safe_payload = safe_log_json(&payload.0);
info!("Received payload: {}", safe_payload);
HttpResponse::Ok().finish()
}
Finally, configure your Actix application to use a logging format that's resistant to injection, such as structured JSON logging with fixed field ordering and proper escaping.