Formula Injection in Axum with Firestore
Formula Injection in Axum with Firestore — how this specific combination creates or exposes the vulnerability
Formula Injection occurs when untrusted user input is placed into a computed field that is later evaluated by an application or spreadsheet-like component. In an Axum application that integrates with Google Firestore, this typically manifests when user-controlled data (e.g., a name or identifier) is stored in Firestore and later rendered into a downloadable spreadsheet (e.g., CSV or Excel) or used in a context where formulas are dynamically constructed.
Consider an endpoint that exports user records as CSV. If the application writes raw input directly into a cell that begins with =, +, or -, spreadsheet applications will interpret the cell content as a formula. For example, a user named =1+1 or '=HYPERLINK("http://malicious.site") can trigger unwanted behavior when the exported file is opened.
In Axum, a handler might collect data from Firestore and serialize it for CSV output without sanitizing string values. Firestore itself does not execute formulas, but the downstream rendering layer (e.g., a browser or spreadsheet program) does. The risk is compounded when Firestore documents contain fields derived from user input, such as calculated totals or labels that are dynamically generated on the client or during export. If an attacker can influence these values, they may inject payloads that execute in the context of the recipient’s system.
Another scenario involves web interfaces that construct URLs or JavaScript based on Firestore documents. For instance, if a Firestore field contains javascript:alert(1) and is interpolated into an HTML attribute or script context, it can lead to stored or reflected XSS. Axum templates or frontend frameworks that bind Firestore data without escaping can inadvertently execute injected code.
Real-world attack patterns mirror the OWASP API Top 10 A03:2023 Injection, where untrusted data is sent to an interpreter as part of a command or query. In this case, the interpreter is a spreadsheet or browser engine. The OWASP Injection classification applies, and specific CVEs related to formula injection in spreadsheet software (e.g., CVE-2017-8559) illustrate the impact of improper input handling.
To detect this with middleBrick, you would submit the Axum endpoint URL for an unauthenticated scan. The tool’s Input Validation and Data Exposure checks would flag unsanitized user input in export routines or template rendering. The LLM/AI Security module would additionally probe for prompt injection if your API interacts with language models, though formula injection primarily concerns data validation and output encoding.
Firestore-Specific Remediation in Axum — concrete code fixes
Remediation focuses on input validation, output encoding, and safe data handling patterns within Axum handlers that interact with Firestore.
- Validate and sanitize inputs before writing to Firestore. Reject or transform values that begin with formula-indicative characters when used in export contexts.
- Encode output based on context. Use proper escaping for CSV, HTML, and JavaScript. Never directly interpolate user data into executable contexts.
- Use structured data exports. Prefer generating files with libraries that enforce safe formatting rather than string concatenation.
Example 1: Safe CSV generation with escaping
use axum::response::IntoResponse;
use csv::Writer;
use std::io::Cursor;
async fn export_users_handler(users: Vec<User>) -> impl IntoResponse {
let mut wtr = csv::Writer::from_writer(Cursor::new(Vec::new()));
for user in users {
// Ensure fields that could start with '=' are escaped
let safe_name = if user.name.starts_with('=') || user.name.starts_with('+') || user.name.starts_with('-') {
format!("='{}", user.name)
} else {
user.name
};
wtr.write_record(&[user.id, safe_name, user.email]).unwrap();
}
let data = wtr.into_inner().unwrap();
let body = String::from_utf8(data).unwrap();
(axum::http::StatusCode::OK, [(axum::http::header::CONTENT_TYPE, "text/csv")]).map(|body| body)
}
Example 2: Firestore document write with sanitized fields
use google_firestore1::api::Document;
use google_firestore1::hyper_rustls::HttpsConnector;
use google_firestore1::{Firestore, oauth2};
async fn save_user_firestore(user: &User) -> Result<(), Box<dyn std::error::Error>> {
let auth = oauth2::ServiceAccountAuthenticator::builder(std::fs::File::open("service-account.json").await?)
.build()
.await?;
let hub = Firestore::new(
hyper::Client::builder().build(hyper_rustls::HttpsConnectorBuilder::new().with_native_roots().https_or_http().enable_all_versions()),
auth
);
let mut doc = Document::default();
// Sanitize fields that could be used in formulas downstream
doc.fields = Some({
let mut map = std::collections::HashMap::new();
map.insert("name".to_string(), {
let mut value = google_firestore1::Value::default();
value.string_value = Some(if user.name.starts_with('=') { format!("='{}", user.name) } else { user.name.clone() });
value
});
map
});
let _ = hub.projects().datasets_documents_create(doc, "projects/my-project/datasets/(default)").doit().await?;
Ok(())
}
Example 3: Template rendering with escaping
use askama::Template;
#[derive(Template)]
#[template(path = "user_profile.html")]
struct UserProfileTemplate {
name: String,
email: String,
}
// In your handler:
async fn profile_handler(user: User) -> impl IntoResponse {
let template = UserProfileTemplate {
name: user.name.replace('=', "="), // Basic neutralization
email: user.email,
};
// Render safely; Askama auto-escapes HTML by default
template.render().unwrap()
}
These examples demonstrate how Axum handlers can safely integrate with Firestore while mitigating formula injection risks through input normalization and context-aware encoding.