Formula Injection in Actix
How Formula Injection Manifests in Actix
Formula Injection in Actix applications typically occurs when user-controlled data is included in Excel/CSV downloads without proper sanitization. This vulnerability allows attackers to embed malicious formulas that execute when the file is opened in spreadsheet applications like Microsoft Excel or LibreOffice Calc.
In Actix applications, this commonly manifests in two ways:
- CSV generation endpoints that dynamically create downloadable reports from database queries
- Excel export functionality that uses libraries like
csvorserdeto serialize data structures
The attack vector works because spreadsheet applications automatically evaluate formulas prefixed with special characters like =, +, -, or @. An attacker can craft input containing =cmd|'/C calc'!A0 which, when placed in a cell, executes the Windows calculator when the file is opened.
Here's a vulnerable Actix endpoint that demonstrates the issue:
use actix_web::{get, web, App, HttpServer, Responder};
use csv::Writer;
#[get("/export")]
async fn export(data: web::Data<AppState>) -> impl Responder {
let mut wtr = Writer::from_writer(vec![]);
wtr.write_record(&["Name", "Email", "Notes"])?;
for user in &data.users {
// Vulnerable: user-controlled data written directly
wtr.write_record(&[&user.name, &user.email, &user.notes])?;
}
let csv_data = String::from_utf8(wtr.into_inner()?)?;
HttpResponse::Ok()
.content_type("text/csv")
.body(csv_data)
}
If user.notes contains =1+1, opening the CSV triggers formula evaluation. The risk is amplified when formulas can access external resources or execute system commands.
Actix-Specific Detection
Detecting Formula Injection in Actix requires both static analysis and runtime scanning. For static analysis, look for patterns where user input flows into CSV/Excel generation without sanitization.
middleBrick's black-box scanner can detect this vulnerability by:
- Analyzing the API's content-type headers for CSV/Excel responses
- Testing endpoints with formula injection payloads
- Checking if the server properly escapes special characters
- Verifying if the application implements any input validation for export functionality
For Actix-specific detection, examine your route handlers that generate downloadable content. Look for these patterns:
// Vulnerable pattern - direct user data serialization
fn export_users(&self) -> HttpResponse {
let mut wtr = Writer::from_writer(vec![]);
for user in self.get_users() {
wtr.write_record(&[&user.name, &user.email, &user.bio])?;
}
// No sanitization of user.bio
}
// Safer pattern - using Actix's built-in sanitization
fn safe_export(&self) -> HttpResponse {
let mut wtr = Writer::from_writer(vec![]);
for user in self.get_users() {
let sanitized_bio = self.sanitize_for_csv(&user.bio);
wtr.write_record(&[&user.name, &user.email, &sanitized_bio])?;
}
}
middleBrick's CLI tool can scan your Actix application's API endpoints:
npx middlebrick scan http://localhost:8080/export
The scanner tests for formula injection by submitting payloads like =1+1, ++CMD, and @SUM(1+1) in all string parameters, then analyzes the generated CSV/Excel for formula evaluation.
Actix-Specific Remediation
Remediating Formula Injection in Actix applications requires a multi-layered approach. The most effective strategy combines input sanitization, output encoding, and safe CSV generation practices.
For Actix applications using the csv crate, implement formula sanitization before writing records:
use csv::Writer;
use actix_web::{get, web, App, HttpServer, Responder};
fn sanitize_csv_input(input: &str) -> String {
let mut result = String::with_capacity(input.len());
// Check if string starts with formula-triggering characters
if input.starts_with(['=', '+', '-', '@']) {
// Prepend apostrophe to force text treatment
result.push_str("'");
}
// Escape double quotes and problematic characters
for c in input.chars() {
if c == '"' {
result.push_str("\"\"\"); // Excel-escape
} else {
result.push(c);
}
}
result
}
#[get("/safe-export")]
async fn safe_export(data: web::Data<AppState>) -> impl Responder {
let mut wtr = Writer::from_writer(vec![]);
wtr.write_record(&["Name", "Email", "Notes"])?;
for user in &data.users {
let safe_name = sanitize_csv_input(&user.name);
let safe_email = sanitize_csv_input(&user.email);
let safe_notes = sanitize_csv_input(&user.notes);
wtr.write_record(&[&safe_name, &safe_email, &safe_notes])?;
}
let csv_data = String::from_utf8(wtr.into_inner()?)?;
HttpResponse::Ok()
.content_type("text/csv")
.body(csv_data)
}
For applications using serde for serialization, implement custom serializers that handle formula injection:
use serde::ser::Serializer;
struct SafeString(&'static str);
impl serde::Serialize for SafeString {
fn serialize(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let sanitized = sanitize_csv_input(self.0);
serializer.serialize_str(&sanitized)
}
}
// Usage in your data structures
#[derive(Serialize)]
struct UserExport {
#[serde(serialize_with = "safe_string")] // custom serializer
name: String,
#[serde(serialize_with = "safe_string")]
email: String,
#[serde(serialize_with = "safe_string")]
notes: String,
}
fn safe_string<S>(value: &str, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let safe = sanitize_csv_input(value);
serializer.serialize_str(&safe)
}
middleBrick's continuous monitoring in the Pro plan can alert you if formula injection vulnerabilities reappear in your CI/CD pipeline, ensuring your Actix application remains secure as it evolves.