JavaScript Application Security: Top 10 Vulnerabilities
Modern JavaScript applications on Node.js process huge amounts of sensitive data, making them an attractive target for attackers. According to OWASP, most hacks occur due to common mistakes that can be prevented. In this article, we will analyze the 10 most common vulnerabilities, provide real code examples, and show how to protect your application.
1. XSS (Cross-Site Scripting)
Cross-site scripting is one of the oldest but still relevant threats. An attacker injects a malicious script into a web page, which executes in the victim's browser.
// Vulnerable codeapp.get('/search', (req, res) => { const query = req.query.q; res.send(`<h2>Search results for: ${query}</h2>`);});
// Safe versionconst escapeHtml = (str) => { return str.replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, ''');};
app.get('/search', (req, res) => { const query = escapeHtml(req.query.q || ''); res.send(`<h2>Search results for: ${query}</h2>`);});2. SQL Injection
SQL injection occurs when user input is directly concatenated into an SQL query. This can lead to data leakage or deletion.
// Vulnerable codeconst userId = req.body.id;const query = `SELECT * FROM users WHERE id = ${userId}`;
// Safe version with parameterized queriesconst userId = req.body.id;const query = 'SELECT * FROM users WHERE id = ?';db.query(query, [userId], (err, results) => { // handle results});3. Insecure Deserialization
Node.js often uses JSON.parse(), but if you work with other formats (e.g., Buffer), an attacker can inject malicious data.
// Dangerous approachconst data = JSON.parse(req.body.data); // may contain __proto__
// Safe approachconst safeParse = (json) => { const data = JSON.parse(json); // Recursively remove dangerous properties return JSON.parse(JSON.stringify(data));};4. CSRF (Cross-Site Request Forgery)
An attack where an attacker forces a user to perform an unwanted action on a site where they are authenticated.
// Setting up protection with csurfconst csrf = require('csurf');const csrfProtection = csrf({ cookie: true });
app.post('/transfer', csrfProtection, (req, res) => { // safe transfer handling});5. Insecure Authentication
Weak passwords, lack of login attempt limiting, and storing passwords in plain text are common issues.
// Proper password hashingconst bcrypt = require('bcrypt');
async function createUser(password) { const salt = await bcrypt.genSalt(12); const hash = await bcrypt.hash(password, salt); // save hash in DB}
async function checkPassword(plainPassword, hash) { return await bcrypt.compare(plainPassword, hash);}6. Sensitive Data Exposure
Sending error stacks to the client or logging passwords is a common developer mistake.
// Incorrectapp.use((err, req, res, next) => { res.status(500).json({ error: err.stack });});
// Correctapp.use((err, req, res, next) => { console.error(err); // log on server res.status(500).json({ error: 'Internal server error' });});7. Insecure Configuration
Using default ports, enabled debug mode in production, and weak CORS policies.
// Safe CORS configurationconst cors = require('cors');cons