You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/pentesting-web/nosql-injection.md
+54-13Lines changed: 54 additions & 13 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -134,21 +134,61 @@ Inject `throw new Error(JSON.stringify(this))` in a `$where` clause to exfiltrat
134
134
{ "$where": "this.username='bob' && this.password=='pwd'; throw new Error(JSON.stringify(this));" }
135
135
```
136
136
137
+
If the application only leaks the first failing document, keep the dump deterministic by excluding documents you already recovered. Comparing against the last leaked `_id` is an easy paginator:
### Beating pre/post conditions in syntax injection
144
+
145
+
When the application builds the Mongo filter as a **string** before parsing it, syntax injection is no longer limited to a single field and you can often neutralize surrounding conditions.
146
+
147
+
In `$where` injections, JavaScript truthy values and poison null bytes are still useful to kill trailing clauses:
148
+
149
+
```javascript
150
+
' || 1 || 'x
151
+
' || 1%00
152
+
```
153
+
154
+
In raw JSON filter injection, duplicate keys can override earlier constraints on parsers that follow a **last-key-wins** policy:
This trick is parser-dependent and only applies when the application assembles JSON with string concatenation/interpolation first. It does **not** apply when the backend keeps the query as a structured object end-to-end.
Versions ≤ 6.0.0 exposed the Meteor method `listEmojiCustom` that forwarded a user-controlled **selector** object directly to `find()`. By injecting operators such as `{"$where":"sleep(2000)||true"}` an unauthenticated attacker could build a timing oracle and exfiltrate documents. The bug was patched in 6.0.1 by validating selector shape and stripping dangerous operators.
172
+
Versions ≤ 6.0.0 exposed the Meteor method `listEmojiCustom` that forwarded a user-controlled **selector** object directly to `find()`. By injecting operators such as `{"$where":"sleep(2000)||true"}` an unauthenticated attacker could build a timing oracle and exfiltrate documents. The bug was patched in 6.0.1 by validating selector shape and stripping dangerous operators.
When `populate()` is used with the `match` option, Mongoose (≤ 8.8.2) copied the object verbatim *before* sending it to MongoDB. Supplying `$where`therefore executed JavaScript **inside Node.js** even if server-side JS was disabled on MongoDB:
If an application forwards attacker-controlled objects into `populate({ match: ... })`, vulnerable Mongoose versions allow `$where`-based search injection inside the populate filter. CVE-2024-53900 covered the top-level case; CVE-2025-23061 covered a bypass where `$where` was nested under operators such as `$or`.
144
176
145
177
```js
146
-
// GET /posts?author[$where]=global.process.mainModule.require('child_process').execSync('id')
The first patch (8.8.3) blocked top-level `$where`, but nesting it under `$or` bypassed the filter, leading to CVE-2025-23061. The issue was fully fixed in 8.9.5, and a new connection option `sanitizeFilter: true` was introduced.
182
+
Use an allow-list and map scalars explicitly instead of forwarding the whole request object. Mongoose also supports `sanitizeFilter` to wrap nested operator objects in `$eq`, but it should be treated as a safety net rather than a replacement for explicit filter mapping:
183
+
184
+
```js
185
+
mongoose.set('sanitizeFilter', true);
186
+
187
+
Post.find().populate({
188
+
path: 'author',
189
+
match: { email: req.query.email }
190
+
});
191
+
```
152
192
153
193
### GraphQL → Mongo filter confusion
154
194
Resolvers that forward `args.filter` directly into `collection.find()` remain vulnerable:
@@ -166,11 +206,11 @@ Mitigations: recursively strip keys that start with `$`, map allowed operators e
166
206
167
207
## Defensive Cheat-Sheet (updated 2025)
168
208
169
-
1. Strip or reject any key that starts with `$` (`express-mongo-sanitize`, `mongo-sanitize`, Mongoose `sanitizeFilter:true`).
170
-
2. Disable server-side JavaScript on self-hosted MongoDB (`--noscripting`, default in v7.0+).
171
-
3. Prefer `$expr` and aggregation builders instead of `$where`.
172
-
4. Validate data types early (Joi/Ajv) and disallow arrays where scalars are expected to avoid `[$ne]` tricks.
173
-
5. For GraphQL, translate filter arguments through an allow-list; never spread untrusted objects.
209
+
1. Strip or reject keys that start with `$`; if Express is in front of Mongo/Mongoose, sanitize `req.body`, `req.query`, and `req.params` before they reach the ORM.
210
+
2. Disable server-side JavaScript on self-hosted MongoDB (`--noscripting` or `security.javascriptEnabled: false`) so `$where` and similar JS sinks are unavailable.
211
+
3. Prefer `$expr` and typed query builders instead of `$where`.
212
+
4. Validate data types early (Joi/Ajv/Zod) and disallow arrays or objects where scalars are expected to avoid `[$ne]` tricks.
213
+
5. For GraphQL, translate filter arguments through an allow-list; never spread untrusted objects into Mongo/Mongoose filters.
0 commit comments