Common Web Security Attacks Every Developer Should Know
When I first started building projects, I thought security was something I’d worry about later — maybe after I mastered React or landed a job.
I couldn’t have been more wrong.
While working on a basic to-do app, a developer friend pointed out that my comment box was vulnerable to an “XSS attack.” I had no idea what that meant — until he showed me how easily someone could inject malicious code through a simple input field.
That moment changed how I thought about web development.
Security isn't an advanced topic reserved for experts — it's foundational knowledge. And the truth is, almost every project I was building — forms, user auth, data displays — had hidden vulnerabilities I didn’t even realize.
If you’re showcasing these projects publicly, it’s crucial to understand the common security attacks that target beginner code. They’re not advanced hacks — just smart ways attackers exploit common mistakes.
Let’s break them down.
1. Cross-Site Scripting (XSS)
XSS attacks inject malicious scripts into trusted websites
XSS happens when an attacker injects malicious scripts into web pages viewed by other users. I learned this the hard way when a simple comment section became a gateway for stolen cookies.
// Vulnerable code - DON'T DO THIS
function displayComment(comment) {
document.getElementById("comments").innerHTML += `<p>${comment}</p>`;
}
// Attacker input: <script>alert('XSS!');</script>
// Result: Script executes on other users' browsers
The Fix:
// Safe approach - escape HTML
function displayComment(comment) {
const safeComment = comment
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
document.getElementById("comments").innerHTML += `<p>${safeComment}</p>`;
}
// Or better yet, use textContent
document.getElementById("comments").textContent = comment;
2. SQL Injection
SQL injection manipulates database queries through user input
This attack manipulates your database queries by injecting malicious SQL code through user inputs. It's like giving attackers a direct line to your database.
-- Vulnerable query - DON'T DO THIS
SELECT * FROM users WHERE email = '" + userInput + "' AND password = '" + password + "'
-- Attacker input: admin@example.com' OR '1'='1' --
-- Resulting query bypasses authentication:
SELECT * FROM users WHERE email = 'admin@example.com' OR '1'='1' --' AND password = ''
The Fix:
// Use parameterized queries
const query = "SELECT * FROM users WHERE email = ? AND password = ?";
db.query(query, [userEmail, hashedPassword], (err, results) => {
// Handle results safely
});
// Or with modern ORMs
const user = await User.findOne({
where: {
email: userEmail,
password: hashedPassword,
},
});
3. Cross-Site Request Forgery (CSRF)
CSRF tricks users into performing unwanted actions on authenticated sites
CSRF tricks users into performing actions they didn't intend to perform. The attacker leverages the user's existing authentication to perform malicious actions.
<!-- Malicious site creates this form -->
<form action="https://yourbank.com/transfer" method="POST">
<input type="hidden" name="amount" value="10000" />
<input type="hidden" name="to" value="attacker-account" />
</form>
<script>
document.forms[0].submit();
</script>
The Fix:
// Implement CSRF tokens
app.use(csrf());
app.get('/transfer', (req, res) => {
res.render('transfer', { csrfToken: req.csrfToken() });
});
// In your form
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="number" name="amount" required>
<button type="submit">Transfer</button>
</form>
4. Insecure Direct Object Reference (IDOR)
IDOR allows unauthorized access to objects by manipulating references
IDOR occurs when applications expose direct references to internal objects without proper authorization checks.
// Vulnerable endpoint - DON'T DO THIS
app.get("/api/documents/:id", (req, res) => {
const document = db.getDocument(req.params.id);
res.json(document); // Anyone can access any document by changing the ID
});
The Fix:
// Proper authorization check
app.get("/api/documents/:id", authenticateUser, (req, res) => {
const document = db.getDocument(req.params.id);
// Verify ownership or permissions
if (document.userId !== req.user.id && !req.user.isAdmin) {
return res.status(403).json({ error: "Access denied" });
}
res.json(document);
});
5. Command Injection
Command injection executes arbitrary system commands through user input
This happens when user input is passed directly to system commands without proper validation.
// Dangerous code - DON'T DO THIS
const { exec } = require("child_process");
app.post("/ping", (req, res) => {
const host = req.body.host;
exec(`ping -c 4 ${host}`, (error, stdout) => {
res.send(stdout);
});
});
// Attacker input: "google.com; rm -rf /"
// Results in: ping -c 4 google.com; rm -rf /
The Fix:
// Input validation and sanitization
const validator = require("validator");
app.post("/ping", (req, res) => {
const host = req.body.host;
// Validate input
if (!validator.isFQDN(host) && !validator.isIP(host)) {
return res.status(400).json({ error: "Invalid host" });
}
// Use safer alternatives or escape properly
const { spawn } = require("child_process");
const ping = spawn("ping", ["-c", "4", host]);
let output = "";
ping.stdout.on("data", (data) => {
output += data;
});
ping.on("close", () => {
res.send(output);
});
});
Quick Security Checklist
Before deploying any feature, I now run through this mental checklist:
- Input Validation: Are all user inputs validated and sanitized?
- Authentication: Are protected routes properly authenticated?
- Authorization: Can users only access their own data?
- Output Encoding: Are dynamic outputs properly escaped?
- HTTPS: Is all sensitive data transmitted securely?
- Dependencies: Are all third-party packages up to date?
Pro Tips from the Trenches
-
Think Like an Attacker: For every input field, ask "What if someone enters malicious code here?"
-
Defense in Depth: Don't rely on a single security measure. Layer your defenses.
-
Fail Securely: When something goes wrong, fail in a way that doesn't expose sensitive information.
-
Keep Learning: Security threats evolve constantly. Stay updated with OWASP Top 10.
Wrapping Up
Security isn't someone else's problem - it's every developer's responsibility. These attacks are preventable with proper coding practices and awareness. The key is building security into your development process from day one, not bolting it on afterward.
Start small: validate inputs, use parameterized queries, implement proper authentication. These simple steps prevent the majority of common attacks.
Remember, the goal isn't to become a security expert overnight, but to write code that doesn't accidentally open doors for attackers. Your users' data depends on it.
Want to dive deeper into web security? Check out the OWASP Top 10 for the most critical security risks facing web applications today.