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(), }
}
Your Actix Web API
might be exposed to NoSQL Injection
74% of Actix Web apps fail this check. Hackers use automated scanners to find this specific flaw. Check your codebase before they do.
Free Tier • No Credit Card • Instant Report
Verified 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.