How to fix NoSQL Injection
in Salvo
Executive Summary
NoSQL Injection in Salvo handlers typically occurs when raw user input is deserialized into a generic map or JSON object and passed directly into a database query filter. In Rust, while the type system provides some protection, using dynamic keys or nested objects in BSON documents allows attackers to inject operators like $gt, $ne, or $regex to bypass authentication or exfiltrate data. To secure Salvo apps, you must enforce strict schema validation using Serde and avoid passing raw JSON values into MongoDB's doc! macro.
The Vulnerable Pattern
use salvo::prelude::*; use mongodb::bson::doc;#[handler] async fn vulnerable_login(req: &mut Request, res: &mut Response) { // DANGER: Parsing into a generic Value allows nested objects let body: serde_json::Value = req.parse_json().await.unwrap();
// If body is {"user": {"$ne": ""}, "pass": {"$ne": ""}}, the query returns the first user let filter = doc! { "username": body["username"].clone(), "password": body["password"].clone() }; let user = db.collection::<User>("users").find_one(filter, None).await; res.render(Json(user));
}
The Secure Implementation
The vulnerability lies in the use of `serde_json::Value`. When the MongoDB driver's `doc!` macro receives a `Value`, it can interpret nested objects as query operators. An attacker can send a JSON payload where the 'username' field is an object `{"$ne": null}`, causing the database to return any user. The fix involves defining a strict Rust `struct` for the request body. By typing the fields as `String`, Serde will fail to deserialize the request if the attacker provides an object instead of a literal string, effectively neutralizing the injection vector before it reaches the database driver.
use salvo::prelude::*; use mongodb::bson::doc; use serde::Deserialize;#[derive(Deserialize)] struct LoginRequest { username: String, password: String, }
#[handler] async fn secure_login(req: &mut Request, res: &mut Response) { // FIX: Parse into a strictly typed struct. // This forces input to be Strings, preventing operator injection. let login: LoginRequest = match req.parse_json().await { Ok(data) => data, Err(_) => return res.status_code(StatusCode::BAD_REQUEST), };
let filter = doc! { "username": &login.username, "password": &login.password }; let user = db.collection::<User>("users").find_one(filter, None).await; res.render(Json(user));
}
Your Salvo API
might be exposed to NoSQL Injection
74% of Salvo 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.