NoSQL Injection
NoSQL injection is a critical security vulnerability that affects NoSQL databases such as MongoDB, CouchDB, and others. Unlike traditional SQL injection, NoSQL injection exploits the query syntax and structure of NoSQL databases to manipulate application behavior and gain unauthorized access to data.
What is NoSQL Injection?
NoSQL injection occurs when user input is directly incorporated into NoSQL database queries without proper validation or sanitization. Attackers can manipulate these queries to:
- Bypass authentication mechanisms
- Extract sensitive data
- Modify or delete database records
- Escalate privileges
- Execute arbitrary code (in some cases)
NoSQL injection can be as devastating as SQL injection, potentially leading to complete database compromise and unauthorized access to sensitive information.
Common NoSQL Database Types and Vulnerabilities
MongoDB Injection
MongoDB is one of the most popular NoSQL databases and is susceptible to various injection attacks:
JavaScript Injection
MongoDB allows JavaScript execution in certain operations. Attackers can inject malicious JavaScript code:
// Vulnerable code
db.users.find({ username: userInput });
// Malicious input: "; return true; //
// Results in: db.users.find({ username: ""; return true; // });
Operator Injection
MongoDB uses operators like $where, $ne, $gt, etc. Attackers can manipulate these:
// Vulnerable authentication check
db.users.find({
username: req.body.username,
password: req.body.password
});
// Malicious payload:
// username: "admin"
// password: { $ne: null }
// This bypasses password validation
CouchDB Injection
CouchDB uses JSON-based queries and can be vulnerable to:
// Vulnerable view query
function(doc) {
if (doc.type === 'user' && doc.name === userInput) {
emit(doc._id, doc);
}
}
// Malicious input can manipulate the view function
Real-World Attack Examples
Authentication Bypass
Consider a Node.js application with MongoDB:
// Vulnerable login function
app.post('/login', (req, res) => {
const { username, password } = req.body;
db.collection('users').findOne({
username: username,
password: password
}, (err, user) => {
if (user) {
res.json({ success: true, message: 'Login successful' });
} else {
res.json({ success: false, message: 'Invalid credentials' });
}
});
});
Attack payload:
{
"username": "admin",
"password": { "$ne": null }
}
This payload exploits the $ne (not equal) operator to bypass password validation.
Data Extraction
// Vulnerable search function
app.get('/search', (req, res) => {
const searchTerm = req.query.q;
db.collection('documents').find({
$where: `this.title.indexOf('${searchTerm}') !== -1`
}).toArray((err, docs) => {
res.json(docs);
});
});
Attack payload:
/search?q=') !== -1 || this.secret || ('
This manipulates the $where clause to potentially expose all documents.
JavaScript Code Execution
// Dangerous use of $where operator
db.users.find({
$where: `this.age > ${userAge}`
});
// Malicious input: "20; return true; //"
// Results in code execution outside the intended logic
Prevention Techniques
1. Input Validation and Sanitization
Always validate and sanitize user input:
const validator = require('validator');
// Validate input types
function validateInput(input) {
if (typeof input !== 'string') {
throw new Error('Invalid input type');
}
// Sanitize special characters
return validator.escape(input);
}
// Safe login function
app.post('/login', (req, res) => {
try {
const username = validateInput(req.body.username);
const password = validateInput(req.body.password);
db.collection('users').findOne({
username: username,
password: password
}, callback);
} catch (error) {
res.status(400).json({ error: 'Invalid input' });
}
});
2. Use Parameterized Queries
Avoid string concatenation in queries:
// BAD: String concatenation
const query = `{ "username": "${username}" }`;
// GOOD: Proper object construction
const query = { username: username };
3. Disable JavaScript Execution
For MongoDB, disable server-side JavaScript when not needed:
// MongoDB configuration
mongod --noscripting
// Or in code, avoid $where operator
// Use explicit field comparisons instead
4. Implement Proper Schema Validation
Use schema validation to restrict allowed data types:
// MongoDB schema validation
db.createCollection('users', {
validator: {
$jsonSchema: {
bsonType: 'object',
required: ['username', 'password'],
properties: {
username: {
bsonType: 'string',
description: 'must be a string and is required'
},
password: {
bsonType: 'string',
description: 'must be a string and is required'
}
}
}
}
});
5. Use Object-Document Mapping (ODM)
ODMs like Mongoose provide built-in protection:
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
validate: {
validator: function(v) {
return /^[a-zA-Z0-9_]+$/.test(v);
},
message: 'Username contains invalid characters'
}
},
password: {
type: String,
required: true
}
});
const User = mongoose.model('User', userSchema);
// Safe query using Mongoose
User.findOne({
username: req.body.username,
password: req.body.password
});
Security Testing for NoSQL Injection
Automated Testing Tools
- NoSQLMap: A specialized tool for NoSQL injection testing
# Install NoSQLMap
git clone https://github.com/codingo/NoSQLMap.git
cd NoSQLMap
python nosqlmap.py -u "http://target.com/login" --data "username=admin&password=admin"
- Burp Suite: Professional web application security testing
- Use Burp's Intruder to test various NoSQL injection payloads
- Custom payload lists for NoSQL operators
Manual Testing Techniques
Test for Operator Injection
// Test payloads for authentication bypass
{
"username": "admin",
"password": { "$ne": null }
}
{
"username": "admin",
"password": { "$regex": ".*" }
}
{
"username": { "$gt": "" },
"password": { "$gt": "" }
}
Test for JavaScript Injection
// Payloads for $where operator
"'; return true; //"
"' || 1==1 //"
"' || this.password //"
Code Review Checklist
When reviewing code for NoSQL injection vulnerabilities:
- Are user inputs directly concatenated into queries?
- Is the
$whereoperator used with user input? - Are query operators (
$ne,$gt, etc.) filtered from user input? - Is input validation performed on all user data?
- Are parameterized queries used consistently?
- Is schema validation implemented?
Real-World Case Studies
Case Study 1: Authentication Bypass in E-commerce
A major e-commerce platform suffered a breach where attackers used NoSQL injection to bypass admin authentication:
Vulnerable code:
db.admins.findOne({
email: req.body.email,
password: req.body.password
});
Attack:
{
"email": "admin@company.com",
"password": { "$ne": null }
}
Impact: Complete admin panel access, customer data exposure
Fix: Implemented input type validation and used Mongoose ODM
Case Study 2: Data Extraction via JavaScript Injection
A social media application allowed profile searches that were vulnerable to JavaScript injection:
Vulnerable code:
db.profiles.find({
$where: `this.bio.includes('${searchTerm}')`
});
Attack:
searchTerm: "') || this.private_data || ('"
Impact: Exposure of private user data
Fix: Replaced $where with regex-based text search using $regex operator
Advanced Protection Strategies
Defense in Depth
Implement multiple layers of security:
- Network Level: Firewall rules, VPN access
- Application Level: Input validation, authentication
- Database Level: User permissions, query restrictions
- Monitoring Level: Anomaly detection, logging
Database Security Configuration
MongoDB Security Best Practices
// Enable authentication
use admin
db.createUser({
user: "admin",
pwd: "strongPassword",
roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
});
// Enable authorization
mongod --auth
// Bind to specific IP
mongod --bind_ip 127.0.0.1
// Enable SSL/TLS
mongod --sslMode requireSSL --sslPEMKeyFile /path/to/ssl.pem
Monitoring and Alerting
Implement monitoring for suspicious database activity:
// Example: Log suspicious query patterns
const suspiciousOperators = ['$where', '$ne', '$regex', '$gt'];
function logSuspiciousActivity(query) {
const queryString = JSON.stringify(query);
suspiciousOperators.forEach(operator => {
if (queryString.includes(operator)) {
console.warn(`Suspicious query detected: ${queryString}`);
// Send alert to security team
}
});
}
Tools and Resources
Security Testing Tools
- NoSQLMap: Automated NoSQL injection testing
- OWASP ZAP: Free security testing proxy
- Burp Suite: Professional web application testing
- MongoDB Compass: Query analysis and monitoring
Security Libraries
// Input validation
const Joi = require('joi');
const validator = require('validator');
// ODM/ORM protection
const mongoose = require('mongoose'); // MongoDB
const nano = require('nano'); // CouchDB
// Security middleware
const helmet = require('helmet');
const express = require('express');
const app = express();
app.use(helmet()); // Security headers
- Never trust user input - Always validate and sanitize
- Use parameterized queries - Avoid string concatenation
- Implement proper authentication - Multi-factor when possible
- Regular security testing - Both automated and manual
- Keep databases updated - Apply security patches promptly
- Monitor database activity - Detect anomalies early
- Follow principle of least privilege - Minimal necessary permissions
Conclusion
NoSQL injection represents a significant security risk in modern applications. By understanding the attack vectors, implementing proper prevention techniques, and maintaining a security-first mindset, developers can protect their applications and data from these sophisticated attacks.
Remember that security is an ongoing process, not a one-time implementation. Regular testing, monitoring, and staying updated with the latest security practices are essential for maintaining a secure application environment.