Nosql Injection in Actix with Api Keys
Nosql Injection in Actix with Api Keys — how this specific combination creates or exposes the vulnerability
Nosql Injection occurs when user-controlled input is interpreted as part of a NoSQL query without proper validation or parameterization. In Actix web applications that use an embedded or external NoSQL store (for example, MongoDB via a Rust driver), this typically arises when query construction directly interpolates request data. If the application also relies on API keys for access control but does not validate or scope those keys against the data queries, the combination can expose both authorization bypass and injection paths.
Consider an Actix handler that retrieves a user profile using a user ID and an API key passed as headers or query parameters:
use actix_web::{get, web, HttpResponse, Responder};
use mongodb::{bson::{doc, Document}, options::ClientOptions, Client};
async fn get_profile(
key: web::Header<actix_web::http::header::Authorization>,
query: web::Query<std::collections::HashMap<String, String>>
) -> impl Responder {
// Example: API key is used only for auth, not for query scoping
let client = Client::with_uri_str("mongodb://localhost:27017").await.unwrap();
let db = client.database("test");
let coll = db.collection("profiles");
// Vulnerable: user-controlled input directly used in document filter
let user_id = query.get("user_id").unwrap_or(&"*".to_string());
let filter = doc! { "_id": user_id };
match coll.find_one(filter, None).await {
Ok(Some(doc)) => HttpResponse::Ok().body(format!("{:?}", doc)),
_ => HttpResponse::NotFound().finish(),
}
}
If the API key is accepted but not used to constrain the query (for example, not used to ensure the caller can only access their own data), an authenticated attacker could manipulate user_id to perform NoSQL Injection. Injection payloads such as { "$ne": null } or { "$where": "return true" } can change query semantics, leading to unauthorized data access or data exfiltration. Because the API key in this scenario is used only for authentication and not bound to the query, the application fails to enforce proper ownership checks, amplifying the impact of a successful injection.
Furthermore, if the API key itself is reflected in logs or error messages, injection can assist in extracting those keys through output manipulation. The risk is not just data exposure but also privilege escalation when the injected query changes permissions or retrieves administrative data. This highlights why API keys in Actix must be tied to query scoping and input handling rather than treated as a one-time gate.
Api Keys-Specific Remediation in Actix — concrete code fixes
Remediation focuses on two goals: preventing NoSQL Injection by treating user input as data, not query fragments, and ensuring API keys are used to enforce data ownership. Below are concrete, safe patterns for Actix with MongoDB.
Never concatenate user input into a BSON document. Parse and validate inputs, then use bound values:
use actix_web::{get, web, HttpResponse, Responder, Result};
use mongodb::{bson::{doc, oid::ObjectId}, options::ClientOptions, Client};
use serde::Deserialize;
#[derive(Deserialize)]
struct ProfileQuery {
user_id: String,
}
async fn get_profile_safe(
key: web::Header<actix_web::http::header::Authorization>,
query: web::Query<ProfileQuery>
) -> Result<impl Responder> {
let client = Client::with_uri_str("mongodb://localhost:27017").await.unwrap();
let db = client.database("test");
let coll = db.collection("profiles");
// Validate and normalize input
let user_id = ObjectId::parse_str(&query.user_id)
.map_err(|_| actix_web::error::ErrorBadRequest("Invalid user ID"))?;
// Scoped query: bind both identity and ownership
let filter = doc! { "_id": user_id };
let opts = None;
match coll.find_one(filter, opts).await {
Ok(Some(doc)) => Ok(HttpResponse::Ok().body(format!("{:?}", doc))),
_ => Ok(HttpResponse::NotFound().finish()),
}
}
By parsing user_id into an ObjectId, we ensure the input matches the expected type and prevents injection via malformed queries.
Do not treat API keys as a simple on/off gate. Use them to scope data access, ensuring a caller cannot read other users’ documents:
use actix_web::HttpRequest;
use mongodb::bson::doc;
async fn get_profile_with_key(
req: HttpRequest,
query: web::Query<ProfileQuery>
) -> Result<impl Responder> {
let client = Client::with_uri_str("mongodb://localhost:27017").await.unwrap();
let db = client.database("test");
let coll = db.collection("profiles");
// Extract API key from headers (example scheme: ApiKey token)
let api_key = req.headers()
.get("X-API-Key")
.and_then(|v| v.to_str().ok())
.ok_or_else(|| actix_web::error::ErrorUnauthorized("Missing API key"))?;
// In practice, validate api_key against a permissions store and derive user_id
let allowed_user_id = lookup_user_for_key(api_key).map_err(|_| actix_web::error::ErrorForbidden("Insufficient scope"))?;
// Enforce ownership in the filter
let filter = doc! {
"_id": ObjectId::parse_str(&query.user_id)?,
"owner_key": api_key
};
let opts = None;
match coll.find_one(filter, opts).await {
Ok(Some(doc)) => Ok(HttpResponse::Ok().body(format!("{:?}", doc))),
_ => Ok(HttpResponse::NotFound().finish()),
}
}
fn lookup_user_for_key(_key: &str) -> Result<mongodb::bson::oid::ObjectId, &'static str> {
// Stub: validate key and return associated user ObjectId
Ok(ObjectId::new())
}
This pattern ensures the API key influences the query filter, preventing horizontal privilege escalation. Combined with input validation, it mitigates both injection and authorization flaws.