Client Area

OWASP Top 10 Practical Defense for PHP and Node.js

ByDomain India Team·DomainIndia Engineering
6 min read24 Apr 20264 views
# OWASP Top 10 Practical Defense for PHP and Node.js
TL;DR
OWASP Top 10 is the canonical list of web app vulnerabilities. Most breaches exploit just 3-4 of them. This guide walks through all 10 with concrete PHP and Node.js defenses you can apply to your DomainIndia-hosted app today.
## The 2021 Top 10 (still current 2026) 1. Broken Access Control 2. Cryptographic Failures 3. Injection (SQL, NoSQL, OS, LDAP) 4. Insecure Design 5. Security Misconfiguration 6. Vulnerable Components 7. Identification & Authentication Failures 8. Software & Data Integrity 9. Logging & Monitoring Failures 10. Server-Side Request Forgery (SSRF) ## A01 — Broken Access Control **The bug:** `GET /api/orders/42` returns order 42 even if it belongs to another user. **Defense (Node.js):** ```javascript app.get('/api/orders/:id', authenticate, async (req, res) => { const order = await db.order.findUnique({ where: { id: req.params.id } }); if (!order) return res.status(404).send('Not found'); // Critical: verify ownership if (order.userId !== req.user.id) { return res.status(403).send('Forbidden'); } res.json(order); }); ``` **Rule:** every resource query filters by `userId = session.userId` OR explicit role check. ## A02 — Cryptographic Failures **The bug:** passwords stored as MD5, SSL optional, secrets in git. **Defenses:** - Passwords: bcrypt or argon2, never MD5/SHA-1 ```php $hash = password_hash($plain, PASSWORD_ARGON2ID); password_verify($plain, $hash); ``` ```javascript import bcrypt from 'bcryptjs'; const hash = await bcrypt.hash(plain, 12); const ok = await bcrypt.compare(plain, hash); ``` - HTTPS everywhere — HSTS header, redirect HTTP→HTTPS (Let's Encrypt is free on DomainIndia) - Encrypt sensitive columns (PII, health data) with AES-256-GCM; keys in env vars, not code - Never commit `.env` — add to `.gitignore` ## A03 — Injection **SQL injection (the classic):** ```php // UNSAFE: $user = $db->query("SELECT * FROM users WHERE id = $id"); // SAFE: $stmt = $db->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$id]); ``` ```javascript // UNSAFE: const user = await db.query(`SELECT * FROM users WHERE id = '${id}'`); // SAFE (parameterised): const user = await db.query('SELECT * FROM users WHERE id = $1', [id]); ``` **NoSQL injection:** ```javascript // UNSAFE — user sends {"$ne": null} as password: db.user.findOne({ email: req.body.email, password: req.body.password }); // SAFE — type check: if (typeof req.body.password !== 'string') return res.status(400).send(); ``` **OS command injection:** ```javascript // NEVER: exec(`convert ${req.body.input} out.png`); // Use execFile with array args: execFile('convert', [req.body.input, 'out.png']); // Still validate input is a safe filename ``` See our [SQL Injection Prevention article](https://domainindia.com/support/kb/preventing-sql-injection-php-nodejs) for depth. ## A04 — Insecure Design Design flaws that no amount of coding fixes. Examples: - Password reset that emails the current password (never store plain) - OTP that's 4 digits with no rate limit (brute-forceable) - "Forgot email" feature that reveals which emails are registered - Assuming client-side validation is enough **Defense:** threat-model your features. Ask "what if attacker tries X?" for every new flow. Test with tools like Burp Suite. ## A05 — Security Misconfiguration Default admin/admin, open dev panels, verbose error pages in production. **Checklist:** - Remove default accounts - Disable dev features in prod (`APP_DEBUG=false`, `NODE_ENV=production`) - Hide server version banners: `server_tokens off;` in nginx - Disable directory listing - Remove `.git` from production webroot - Security headers: ```php header('X-Frame-Options: DENY'); header('X-Content-Type-Options: nosniff'); header('Referrer-Policy: strict-origin-when-cross-origin'); header("Content-Security-Policy: default-src 'self'"); header('Strict-Transport-Security: max-age=31536000; includeSubDomains'); ``` Or use `helmet` in Express / `Helmet` in Laravel. ## A06 — Vulnerable Components Old libraries with known CVEs. **Defense — automate scanning:** ```bash # Node.js npm audit npm audit fix # PHP Composer composer audit # Container scanning trivy image your-app:latest ``` Run on every CI build. Block merge on high-severity issues. Dependabot (GitHub) and Renovate (GitLab/self-hosted) auto-create PRs for updates. ## A07 — Identification & Auth Failures - No rate limiting on login → brute force - Weak passwords allowed - No MFA - Session fixation **Defenses:** ```javascript // Rate limit login import rateLimit from 'express-rate-limit'; app.post('/login', rateLimit({ windowMs: 15 * 60 * 1000, max: 10, message: 'Too many attempts', }), loginHandler); // Enforce strong password const passwordPolicy = { minLength: 12, requireNumber: true, requireSymbol: true, }; // MFA — TOTP via speakeasy import speakeasy from 'speakeasy'; const secret = speakeasy.generateSecret(); // store in DB const token = speakeasy.totp({ secret: user.mfaSecret, encoding: 'base32' }); speakeasy.totp.verify({ secret, encoding: 'base32', token: req.body.code }); ``` Rotate session ID on login (prevents fixation). ## A08 — Software & Data Integrity Downloading dependencies / updates without verification. **Defenses:** - Pin package versions, use lockfiles (`package-lock.json`, `composer.lock`) - Verify signatures on Docker images + system packages - Sign your releases (Sigstore, GPG) - Subresource Integrity (SRI) on 3rd-party scripts: ```html ``` ## A09 — Logging & Monitoring Failures If you don't log it, you can't detect it. Log (minimum): - Auth events (login success/fail, MFA) - Admin actions - Payment events - Security-sensitive API calls - 5xx errors Ship to central system (see our [Observability article](https://domainindia.com/support/kb/production-observability-prometheus-grafana-loki-vps)). Set up alerts: - 10+ failed logins for single user - 100+ 5xx errors per minute - Unusual geographic pattern (login from India, then Nigeria 5 min later) ## A10 — Server-Side Request Forgery (SSRF) App fetches URL → attacker supplies `http://169.254.169.254/latest/meta-data/` (AWS metadata) or `http://internal-db:5432`. **Defense:** ```javascript import { URL } from 'url'; const BLOCKED = [ /^10./, /^172.(1[6-9]|2[0-9]|3[0-1])./, /^192.168./, // private IPs /^169.254./, /^127./, /^::1$/, /^localhost$/, ]; function safeFetch(userUrl) { const url = new URL(userUrl); if (url.protocol !== 'https:') throw new Error('HTTPS only'); // Resolve hostname → IP, check against blocked list const ips = await dns.resolve(url.hostname); if (ips.some(ip => BLOCKED.some(re => re.test(ip)))) { throw new Error('Blocked IP'); } return fetch(url); } ``` Don't let user input determine which URL your server fetches without validation. ## Security testing **Free tools:** - OWASP ZAP (automated scanner) - Burp Suite Community (manual testing) - `nikto` (web server scanner) - `sqlmap` (SQL injection tester — on your OWN apps) - `trivy` (container CVE scanner) **Commercial:** - Snyk, Semgrep, SonarQube — SAST in CI - Detectify, Pentera — DAST + attack simulation Run scans weekly. Fix high-severity within 7 days, critical immediately. ## Common pitfalls ## FAQ
Q Do I need a dedicated security team?

Small team: one engineer doing OWASP scans + annual external pentest is enough. Growing: hire a security engineer at ~50 employees.

Q What's "defense in depth"?

Multiple layers — WAF + input validation + auth + logging + intrusion detection. One layer failing doesn't breach the system.

Q Is Cloudflare WAF enough?

Blocks most automated attacks, not custom/targeted ones. Pair with app-level security.

Q How often should I update dependencies?

Critical security patches: within 24 hours. Regular updates: weekly for dev, monthly batch for prod with testing.

Q ISO 27001 / SOC 2 — do I need it?

Selling to enterprises? Yes. Serving small businesses only? Informal security hygiene is enough.

Harden your DomainIndia-hosted app against OWASP Top 10. Get started

Was this article helpful?

Your feedback helps us improve our documentation

Still need help? Submit a support ticket