Fix NoSQL Injection in Actix Web
NoSQL injection in Actix Web occurs when raw BSON documents are constructed using untrusted input from `serde_json::Value` or loosely typed maps. Attackers leverage operators like `$gt`, `$ne`, or `$regex` to bypass logic. If you're piping raw JSON bodies directly into the `doc!` macro, you're handing over the keys to your database.
The Vulnerable Pattern
use actix_web::{post, web, HttpResponse, Responder}; use mongodb::bson::doc;#[post(“/login”)] async fn login(db: web::Datamongodb::Database, body: web::Json<serde_json::Value>) -> impl Responder { // VULNERABLE: body[“username”] could be {“$ne”: null} // This allows an attacker to bypass authentication by providing a query operator instead of a string. let filter = doc! { “username”: body.get(“username”).unwrap_or(&serde_json::Value::Null), “password”: body.get(“password”).unwrap_or(&serde_json::Value::Null) };
match db.collection::<serde_json::Value>("users").find_one(filter, None).await { Ok(Some(_)) => HttpResponse::Ok().body("Logged in"), _ => HttpResponse::Unauthorized().finish(), }
}
The Secure Implementation
The vulnerability exists because MongoDB's `doc!` macro accepts BSON types converted from `serde_json::Value`. When an attacker sends a JSON object where a string is expected, the driver interprets it as a query operator. For instance, sending `{"username": {"$ne": ""}}` makes the query match any user with a non-empty username. The fix is Type Safety: by defining a `struct` with `String` fields for your request body, `serde-json` will reject any input that isn't a literal string, effectively neutralizing operator injection at the deserialization layer.
use actix_web::{post, web, HttpResponse, Responder}; use mongodb::bson::doc; use serde::Deserialize;#[derive(Deserialize)] struct AuthRequest { username: String, password: String, }
#[post(“/login”)] async fn login(db: web::Datamongodb::Database, body: web::Json
) -> impl Responder { // SECURE: Strict typing via AuthRequest struct ensures username and password are Strings. // Serde will fail to deserialize if the attacker sends an object like {“$ne”: ""}. let filter = doc! { “username”: &body.username, “password”: &body.password }; match db.collection::<serde_json::Value>("users").find_one(filter, None).await { Ok(Some(_)) => HttpResponse::Ok().body("Logged in"), _ => HttpResponse::Unauthorized().finish(), }
}
Protect your Actix Web API
Don't rely on manual checks. GuardAPI's Gemini 3 Pro engine detects NoSQL Injection and logic flaws in seconds.
Run Automated AuditVerified by Ghost Labs Security Team
This content is continuously validated by our automated security engine and reviewed by our research team. Ghost Labs analyzes over 500+ vulnerability patterns across 40+ frameworks to provide up-to-date remediation strategies.