Web Application SecurityAdvanced
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
## 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
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.
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
Related Articles
Security Headers Explained: CSP, HSTS, X-Frame-Options, and More51
CSRF Tokens Deep Dive: PHP, Laravel, Express, and SameSite Cookies32
Secure Password Hashing: bcrypt, argon2, and What Never to Use27
Preventing SQL Injection in PHP and Node.js (with Real Code Examples)23
Preventing XSS (Cross-Site Scripting) in PHP and Node.js23