Eliminating the OWASP Top 10: Code First Perspective
Published:
In CTFs and pentests, you often hunt for vulnerabilities like these—poking at parameters, trying payloads, chaining odd behaviors until something pops. That’s exciting work, but in production engineering, the same issues keep resurfacing not because they’re “hard to find” but because teams don’t fully internalize how they happen. Shortcuts, copy-pasted patterns, and misunderstood framework defaults quietly embed risk into products.
A mature product security practice flips this around: instead of chasing bugs after they ship, we teach engineers the causal story—how an insecure coding habit leads, step by step, to a concrete exploit. When security and engineering share that mental model, prevention becomes natural, and OWASP Top 10 issues (and most others) stop showing up in code reviews and releases. This playbook is written for that goal: each entry starts with a clear, causal explanation of the attack vector and its real-world impact, then contrasts insecure vs secure code, and closes with guardrails that make the safe path the easiest path to follow.
Snippets use Node/Express and vanilla JS, but the patterns apply to any stack.
Index
- Cross-Site Scripting (XSS): Stored, Reflected, DOM, and Mutation-XSS
- Cross-Site Request Forgery (CSRF)
- Insecure Direct Object Reference (IDOR/BOLA)
- SQL / NoSQL / LDAP Injection
- Server-Side Request Forgery (SSRF)
- Server-Side Template Injection (SSTI)
- XML External Entities (XXE)
- Command Injection
- Broken Access Control
- Sensitive Data Exposure & Session Weaknesses
- Security Misconfiguration
- Abuse / DoS & Rate Limiting
- SDLC Guardrails
1) Cross-Site Scripting (XSS): stored, reflected, DOM (and mutation-XSS)
XSS happens when untrusted input becomes executable in a browser—via server-rendered HTML (stored/reflected) or client-side DOM manipulation (DOM-XSS). It leads to account takeover (session theft), data exfiltration, clickjacking helpers, and persistent defacements. Mutation-XSS (mXSS) can even turn “sanitized” HTML into script through browser DOM mutations.
Insecure pattern
// Server renders user-provided HTML directly
const html = await db.getComment(req.params.id);
res.send(`<div class="comment">${html}</div>`); //
Secure pattern
// 1) Auto-escape in templates or escape explicitly
const safe = escapeHtml(await db.getComment(req.params.id));
res.send(`<div class="comment">${safe}</div>`);
// 2) Frontend: never assign untrusted strings to innerHTML
el.textContent = serverValue; // not innerHTML
// 3) Add a restrictive CSP to limit damage if something slips
res.set('Content-Security-Policy',
"default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'");
Guardrails
Ban dangerous sinks (innerHTML, document.write, Function, eval) via lint rules/CI.
Treat URL parts (location.search/hash) as tainted; never flow them into sinks without encoding.
Prefer frameworks with auto-escaping templates by default.
2) Cross-Site Request Forgery (CSRF)
CSRF abuses a victim’s ambient credentials (cookies) to perform unwanted actions on state-changing endpoints. It causes unauthorized money transfers, email/phone changes, privilege flips, and mass data changes—all “by the user’s browser.”
Insecure pattern
app.post('/settings/email', requireAuth, async (req,res) => {
await users.updateEmail(req.user.id, req.body.email); // no CSRF check
res.sendStatus(204);
});
Secure pattern
// Issue + verify anti-CSRF tokens bound to session
const csrfProtection = csrf({ cookie: true });
app.post('/settings/email',
requireAuth,
csrfProtection,
async (req,res)=>{ ... }
);
// Harden cookies
res.cookie('sid', sid, { httpOnly:true, secure:true, sameSite:'Lax' }); // 'Strict' if UX allows
// Also verify Origin/Referer on state-changing requests
Guardrails
All state changes require a valid CSRF token and come over POST/PUT/PATCH/DELETE.
Default cookies to SameSite=Lax/Strict; avoid GETs that mutate state.
3) Insecure Direct Object Reference (IDOR/BOLA)
IDOR appears when the app trusts client-supplied identifiers (userId/orderId/tenantId) without verifying that the requester owns or may access the resource. It leads to horizontal data leaks and privilege creep.
Insecure pattern
app.get('/api/users/:id', requireAuth, async (req,res)=>{
const user = await db.users.findById(req.params.id); // trusts URL id
res.json(user);
});
Secure pattern
app.get('/api/users/:id', requireAuth, async (req,res)=>{
const isSelf = req.user.id === req.params.id;
const isAdmin = req.user.roles.includes('admin');
if (!isSelf && !isAdmin) return res.sendStatus(403);
res.json(await db.users.findById(req.params.id));
});
Guardrails
Centralize object-level authorization (RBAC/ABAC/OPA) and call it for every resource access.
Never rely on “is authenticated” alone; require a subject-to-object check.
4) SQL/NoSQL/LDAP Injection
Injection occurs when untrusted data is concatenated into queries/commands for interpreters. It leads to data dumps, tampering, auth bypass, and remote code execution.
Insecure pattern
const rows = await db.query(
`SELECT * FROM accounts WHERE owner='${req.user.id}' AND name='${req.query.name}'`); //
Secure pattern
// Parameterized queries
const rows = await db.query(
'SELECT * FROM accounts WHERE owner=$1 AND name=$2',
[req.user.id, req.query.name]
);
// For NoSQL: use driver operators, not string concatenation
await mongo.collection('users').findOne({ _id: new ObjectId(req.params.id) });
Guardrails
Only use drivers that default to parameters; ban string-built queries in CI.
Least-privilege DB accounts; restrict network paths to databases.
5) Server-Side Request Forgery (SSRF)
SSRF abuses server features that fetch remote URLs. It allows attackers to pivot into your internal network, hit metadata services, scan ports, and exfiltrate secrets.
Insecure pattern
app.post('/fetch', async (req,res)=>{
const r = await fetch(req.body.url); // raw URL
res.send(await r.text());
});
Secure pattern
const ALLOW_HOSTS = new Set(['api.example.com','cdn.example.com']);
app.post('/fetch', async (req,res)=>{
const u = new URL(req.body.url);
if (!ALLOW_HOSTS.has(u.hostname)) return res.sendStatus(400);
// Resolve & pin DNS; refuse private/loopback/link-local IPs; block redirects to internal
const r = await safeProxyFetch(u, { maxSize: 1_000_000, timeoutMs: 3000 });
res.type('text/plain').send(await r.text());
});
Guardrails
Prefer client-direct uploads (pre-signed URLs) over server fetches.
Allowlist hosts, enforce egress proxy, block private IPs/redirects, set strict timeouts.
6) Server-Side Template Injection (SSTI)
SSTI happens when untrusted input is compiled or evaluated inside server templates. It can lead to arbitrary code execution.
Insecure pattern
// Handlebars/EJS: triple-stache disables escaping
res.render('profile', { bio: req.body.bio }); // template uses {{{bio}}}
Secure pattern
- Keep auto-escaping on (
{{ bio }}
, not{{{ bio }}}
) and never eval in templates.
Guardrails
- Static analysis for
{{{
or<%=
without escape filters. - Sandbox template engines where possible.
7) XML External Entities (XXE)
XXE arises when XML parsers resolve external entities/DTDs. It enables file reads, SSRF, and DoS.
Insecure pattern
const xml = await xml2js.parseStringPromise(req.body); //
Secure pattern
Disable DTD and external entity resolution in parser settings.
Prefer JSON where possible.
Guardrails
- Wrap XML parsing in hardened libraries; ban raw parser use.
8) Command Injection
Command injection happens when user data is passed to a shell command. It can compromise the entire host.
Insecure pattern
const { exec } = require('child_process');
exec(`nslookup ${req.query.host}`, (e, out)=>res.send(out)); //
Secure pattern
const { execFile } = require('child_process');
execFile('nslookup', [req.query.host], { shell:false }, (e, out)=>res.send(out));
Guardrails
Prefer library APIs over shelling out.
Use execFile with whitelisted arguments.
9) Broken Access Control
Mixing up authentication with authorization, trusting client-side roles, or skipping checks can lead to privilege escalation.
Insecure pattern
if (req.user.isAdmin) { purgeAllData(); } //
Secure pattern
Centralize server-side policy evaluation (RBAC/ABAC/OPA).
Deny by default.
Guardrails
Use a single can(subject, action, resource) helper everywhere.
Add policy tests for each endpoint.
10) Sensitive Data Exposure & Session Weaknesses
Sensitive data leaks via plaintext transport, logs, or weak crypto. Session weaknesses lead to hijacking.
Insecure pattern
res.cookie('sid', sessionId); // default cookie flags
Secure pattern
res.cookie('sid', sessionId, { httpOnly:true, secure:true, sameSite:'Lax' });
res.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
Guardrails
TLS everywhere; HSTS on; secrets in a vault.
Limit response fields; never echo secrets.
11) Security Misconfiguration
Defaults kill: debug modes left on, open S3 buckets, permissive CORS, verbose banners.
Insecure pattern
CORS: Access-Control-Allow-Origin: * // with credentials
Secure pattern
Principle of least privilege everywhere.
Tight CORS; remove banners; enable security headers.
Guardrails
Config as code + baseline scanners (CIS).
Separate secrets/configs per environment.
12) Abuse/DoS & Rate Limiting
Endpoints can be abused even when correct. Floods and expensive queries cause outages.
Insecure pattern
app.get('/search', async (req,res)=> res.json(await search(req.query.q))); //
Secure pattern
app.get('/search', rateLimit({ windowMs: 60_000, max: 120 }), async (req,res)=>{
const q = String(req.query.q||'').slice(0,128);
res.json(await search(q, { limit: 25, timeoutMs: 1000 }));
});
Guardrails
- Throttles, quotas, circuit breakers, and caching.
SDLC Guardrails
Design reviews: model threats, map assets, define permissions.
Secure defaults: auto-escaping templates, CSP, CSRF middleware.
CI/CD checks: lint for dangerous sinks, dependency scanning.
Edge controls: API gateway auth, throttling, mTLS.
Observability: structured logs (no PII), security events, regression tests.
Vulnerability | Secure Practice |
---|---|
XSS | textContent + CSP; ban unsafe sinks |
CSRF | Tokens + SameSite cookies + Origin checks |
IDOR | Per-object authorization (authZ) |
Injection | Parameterized queries |
SSRF | Allowlist + proxy + block private IPs |
SSTI | No dynamic eval |
XXE | Disable DTD/external entities |
Command Injection | execFile with whitelist |
Access Control | Centralized, deny by default |
Data/Session | TLS/HSTS, secure cookies, rotate sessions |
Misconfiguration | Hardening, CORS, headers |
Abuse/DoS | Rate limits, pagination, timeouts |