VNX-943: NoSQL injection in a MongoDB query (Python, PHP, Java)
Reports MongoDB queries built from untrusted data in Python, PHP and Java: $where clauses built from formatted strings, filters taken from a parsed request body or a PHP request array, and JSON query strings built by concatenation.
| Field | Value |
|---|---|
| Rule ID | VNX-943 |
| Severity | High |
| SARIF level | error |
| Kind | sast |
| Languages | Python, PHP, Java |
| CWE | CWE-943 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:H/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:H/DI:H/EX:H/EC:N/P:H |
| Tags | nosql-injection mongodb injection cwe-943 |
| Source | internal/sast/rules/vnx-943.rego |
Overview
VNX-943 maps to CWE-943: Improper Neutralization of Special Elements in Data Query Logic. It reports a MongoDB query whose structure an attacker can control:
- Python (pymongo, motor): a
$whereclause built with an f-string,+,%or.format(); or the filter passed tofind,find_one,find_one_and_*,update_*,delete_*,replace_one,count_documentsoraggregatethat is, or contains, the parsed JSON request body (request.json,request.get_json(),json.loads(request.data)), directly or through a variable assigned from it a few lines above. - PHP (mongodb extension or library): a query array given a
$_GET/$_POST/$_REQUEST/$_COOKIEvalue (PHP parsesname[$ne]=xinto an array, which becomes an operator), or a'$where'built by concatenation or interpolation. - Java: a
$whereorFilters.where()built by concatenation, orDocument.parse,BasicDBObject.parse,BsonDocument.parseornew BasicQuerygiven a JSON string built by concatenation orString.format.
Not reported: values cast to a string or number (str(), int(), ObjectId(), (string), strval()), {"$eq": ...} wrappers, Flask request.args values (always strings), constant $where clauses, typed filter builders (Filters.eq, Criteria), ORM find() calls. Node.js MongoDB filters are reported by VNX-NODE-014.
Why This Matters
A request body such as {"username": "admin", "password": {"$ne": null}} turns an equality match into an operator and logs in without a password. $where runs JavaScript on the database server, so any interpolated value can rewrite the predicate.
What Gets Flagged
user = users.find_one({"username": request.json["username"], "password": request.json["password"]})
cursor = db.orders.find({"$where": f"this.owner == '{owner}'"})
$user = $users->findOne(['username' => $_POST['username'], 'password' => $_POST['password']]);
Document filter = Document.parse("{ username: '" + user + "' }");
return orders.find(Filters.where("this.level > " + level));
Remediation
- Cast each request value to the type the field holds (
str(),ObjectId(),(string)) or wrap it in{"$eq": value}. - Replace
$wherewith ordinary query operators that take the value as data. - In Java, build filters with
Filters.eq/Criteria.where(...).is(...)instead of parsing string-built JSON.
References
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-943
// nosec vnx-943
Or record an ignore rule: vulnetix ignore add --rule vnx-943 --reason "...". See Suppressing findings.