Injection Flaws in Flask with Mongodb
Injection Flaws in Flask with Mongodb — how this specific combination creates or exposes the vulnerability
Injection flaws occur when untrusted data is interpreted as part of a command or query. In a Flask application using Mongodb, the typical pattern is to build queries from request parameters (e.g., request.args or request.json) and pass them to PyMongo or MongoEngine. If user input is concatenated into dictionary filters or used to construct dynamic queries without validation, an attacker can manipulate query logic. For example, a login route that does db.users.find_one({"username": username, "password": password}) is safe only if username and password are treated as literal values. However, if a developer builds the filter dynamically—such as {field: user_input} where field is taken from user input—an attacker can supply a JSON-like payload to change query semantics.
With Mongodb, certain operators such as $ne, $in, $where, and $regex can be abused to bypass intended filtering. Consider a search endpoint that builds a filter like {"name": {"$regex": user_supplied_pattern}}. If the pattern is not strictly constrained, an attacker can use patterns like .* to cause a collection scan or exfiltrate data through timing differences. Additionally, if the application inadvertently allows dot notation or positional operators in user-controlled keys, it can lead to unintended field access or updates in write operations, even when the app is only reading data in some contexts.
Flask itself does not enforce any schema or query structure, which means developers must explicitly validate and sanitize inputs before they reach the database. The lack of built-in query validation in Flask, combined with Mongodb’s expressive query syntax, increases the likelihood of injection-style vulnerabilities when developers assume that the database will safely interpret user input. Common root causes include: directly passing request parameters into find or aggregate, using Python string formatting to build queries, and failing to whitelist allowed fields for sorting or filtering.
Real-world attack patterns mirror the OWASP API Top 10 #1 Broken Object Level Authorization when injection enables unauthorized data access. For instance, an attacker might supply {"$ne": "null"} as a value to force a match different from null, or use {"$in": [...]} to attempt enumeration. In aggregation pipelines, injection can appear via the $where operator or JavaScript evaluation if user input is passed into pipeline stages. These techniques can bypass authentication checks or dump collections when endpoints reflect database behavior in error messages.
Because middleBrick scans the unauthenticated attack surface and tests inputs across multiple security checks—including Input Validation and BOLA/IDOR—it can identify endpoints where query construction is too permissive. The scanner does not fix the code, but its findings highlight exactly where user-controlled data reaches the database layer and how an attacker might manipulate query semantics.
Mongodb-Specific Remediation in Flask — concrete code fixes
Remediation centers on strict input validation, whitelisting, and avoiding dynamic query construction. Prefer parameterized queries and explicit field selection. Below are concrete, working Flask examples demonstrating safe patterns.
Safe Query Building with Whitelisted Fields
Only allow known, safe fields for filtering and sorting. Reject or ignore any keys not in an explicit allowlist.
from flask import Flask, request, jsonify
from pymongo import MongoClient
app = Flask(__name__)
client = MongoClient("mongodb://localhost:27017")
db = client["mydb"]
ALLOWED_FILTER_FIELDS = {"name", "email", "status"}
ALLOWED_SORT_FIELDS = {"name", "created_at"}
@app.route("/users")
def list_users():
filter_dict = {}
for key in request.args:
if key in ALLOWED_FILTER_FIELDS:
filter_dict[key] = request.args[key]
# Explicitly ignore disallowed keys instead of trying to parse them
sort_list = []
for key in request.args.get("sort", "").split(","):
key = key.strip()
if key.lstrip("-") in ALLOWED_SORT_FIELDS:
sort_list.append((key, 1 if not key.startswith("-") else -1))
cursor = db.users.find(filter_dict).sort(sort_list) if sort_list else db.users.find(filter_dict)
results = [{"name": doc["name"], "email": doc["email"]} for doc in cursor]
return jsonify(results)
Using $in with a Strictly Typed Value
When using operators like $in, ensure the value is a list of expected types and validate each element.
@app.route("/users/status")
def users_by_status():
statuses = request.args.getlist("status")
# Only allow known status strings
valid_statuses = {"active", "inactive", "pending"}
filtered = [s for s in statuses if s in valid_statuses]
if not filtered:
return jsonify([]), 200
users = db.users.find({"status": {"$in": filtered}})
return jsonify([{"email": u["email"]} for u in users])
Avoiding Dynamic Keys and Operator Injection
Never allow user input to become a key in a filter dict. Also, do not accept raw operator strings from clients.
@app.route("/users/search")
def safe_search():
# BAD: directly using user input as a key
# query = {request.args.get("field"): request.args.get("value")}
# SAFE: map input to known fields
field_map = {
"n": "name",
"e": "email"
}
field = field_map.get(request.args.get("field"))
value = request.args.get("value")
if not field or value is None:
return jsonify({"error": "invalid parameters"}), 400
# Further validate value format if needed
result = db.users.find({field: value}, {"_id": 0, "name": 1, "email": 1})
return jsonify([dict(row) for row in result])
Defensive Aggregation and Server-side JavaScript
Avoid passing user input into aggregation stages that could invoke $where or JavaScript evaluation. If you must use regex, compile it on the server with strict limits and avoid interpolating raw user strings into regex patterns supplied by clients.
import re
@app.route("/users/name_pattern")
def name_pattern_search():
pattern = request.args.get("pattern", "")
# Reject dangerous patterns
if "\\" in pattern or "^" in pattern or "$" in pattern:
return jsonify({"error": "pattern not allowed"}), 400
# Build a safe regex with anchors and no user-controlled flags
try:
regex = re.compile(f"^{re.escape(pattern)}$", re.IGNORECASE)
except re.error:
return jsonify({"error": "invalid regex"}), 400
cursor = db.users.find({"name": {"$regex": regex}})
return jsonify([{"name": doc["name"]} for doc in cursor])
These patterns ensure that user input never directly becomes query structure. By combining allowlists, strict type checks, and avoiding dynamic keys, you reduce the risk of injection-style manipulation in Flask with Mongodb. middleBrick can validate that your endpoints follow these safe practices by scanning unauthenticated routes and flagging permissive query construction.