Skip to main content

OWASP Top 10 Security Vulnerabilities Tutorial

Overview​

The OWASP (Open Web Application Security Project) Top 10 is a standard awareness document representing the most critical web application security risks. This tutorial provides detailed explanations, examples, and mitigation strategies for each vulnerability.

OWASP Top 10 2021​

A01:2021 – Broken Access Control​

Risk: Unauthorized access to functionality and/or data

Common Vulnerabilities:

  • Bypassing access control through URL manipulation
  • Privilege escalation
  • Viewing or editing someone else's account
  • Missing access controls for POST, PUT, DELETE

Vulnerable Code Example:

// Insecure Direct Object Reference
app.get('/user/:id/profile', (req, res) => {
const userId = req.params.id;
// No authorization check!
const user = getUserById(userId);
res.json(user);
});

// Privilege escalation vulnerability
app.post('/admin/delete-user', (req, res) => {
// Only checks if user is logged in, not if they're admin
if (req.user) {
deleteUser(req.body.userId);
res.json({ success: true });
} else {
res.status(401).json({ error: 'Unauthorized' });
}
});

Secure Implementation:

// Proper access control
const authorize = (requiredRole) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}

if (req.user.role !== requiredRole && requiredRole !== 'any') {
return res.status(403).json({ error: 'Insufficient privileges' });
}

next();
};
};

// Resource ownership check
const checkOwnership = async (req, res, next) => {
const resourceId = req.params.id;
const resource = await getResourceById(resourceId);

if (!resource) {
return res.status(404).json({ error: 'Resource not found' });
}

if (resource.ownerId !== req.user.id && req.user.role !== 'admin') {
return res.status(403).json({ error: 'Access denied' });
}

req.resource = resource;
next();
};

// Secure routes
app.get('/user/:id/profile', authenticate, checkOwnership, (req, res) => {
res.json(req.resource);
});

app.post('/admin/delete-user', authenticate, authorize('admin'), (req, res) => {
deleteUser(req.body.userId);
res.json({ success: true });
});

A02:2021 – Cryptographic Failures​

Risk: Exposing sensitive data due to cryptographic failures

Common Issues:

  • Data transmitted in clear text
  • Old or weak cryptographic algorithms
  • Weak random number generation
  • Misusing cryptography

Vulnerable Practices:

// Weak password hashing
const crypto = require('crypto');

// BAD: Using MD5
const weakHash = crypto.createHash('md5').update(password).digest('hex');

// BAD: Using SHA1 without salt
const sha1Hash = crypto.createHash('sha1').update(password).digest('hex');

// BAD: Custom encryption
function customEncrypt(text, key) {
let encrypted = '';
for (let i = 0; i < text.length; i++) {
encrypted += String.fromCharCode(text.charCodeAt(i) ^ key.charCodeAt(i % key.length));
}
return encrypted;
}

Secure Cryptographic Implementation:

const bcrypt = require('bcrypt');
const crypto = require('crypto');

// Secure password hashing
class PasswordService {
static async hash(password) {
const saltRounds = 12;
return await bcrypt.hash(password, saltRounds);
}

static async verify(password, hash) {
return await bcrypt.compare(password, hash);
}

static generateSecureRandom(length = 32) {
return crypto.randomBytes(length).toString('hex');
}
}

// AES-256-GCM encryption
class EncryptionService {
constructor(key) {
this.key = key || crypto.randomBytes(32);
this.algorithm = 'aes-256-gcm';
}

encrypt(plaintext) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipher(this.algorithm, this.key);
cipher.setAAD(Buffer.from('authenticated-data'));

let ciphertext = cipher.update(plaintext, 'utf8', 'hex');
ciphertext += cipher.final('hex');

const authTag = cipher.getAuthTag();

return {
iv: iv.toString('hex'),
ciphertext: ciphertext,
authTag: authTag.toString('hex')
};
}

decrypt(encryptedData) {
const decipher = crypto.createDecipher(this.algorithm, this.key);
decipher.setAAD(Buffer.from('authenticated-data'));
decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex'));

let plaintext = decipher.update(encryptedData.ciphertext, 'hex', 'utf8');
plaintext += decipher.final('utf8');

return plaintext;
}
}

// TLS/HTTPS configuration
const https = require('https');
const fs = require('fs');

const options = {
key: fs.readFileSync('path/to/private-key.pem'),
cert: fs.readFileSync('path/to/certificate.pem'),
// Enforce strong TLS configuration
ciphers: [
'ECDHE-RSA-AES128-GCM-SHA256',
'ECDHE-RSA-AES256-GCM-SHA384',
'ECDHE-RSA-AES128-SHA256',
'ECDHE-RSA-AES256-SHA384'
].join(':'),
honorCipherOrder: true,
secureProtocol: 'TLSv1_2_method'
};

https.createServer(options, app).listen(443);

A03:2021 – Injection​

Risk: Untrusted data sent to an interpreter as part of a command or query

SQL Injection Example:

# Vulnerable code
import sqlite3

def get_user(username, password):
conn = sqlite3.connect('users.db')
cursor = conn.cursor()

# DANGEROUS: String concatenation
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
cursor.execute(query)

return cursor.fetchone()

# Attack payload: username = "admin'--", password = "anything"
# Resulting query: SELECT * FROM users WHERE username='admin'--' AND password='anything'

Secure SQL Implementation:

import sqlite3
from typing import Optional, Dict, Any

class UserService:
def __init__(self, db_path: str):
self.db_path = db_path

def get_user(self, username: str, password_hash: str) -> Optional[Dict[Any, Any]]:
"""Secure user retrieval using parameterized queries"""
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row # Return rows as dictionaries
cursor = conn.cursor()

try:
# Use parameterized query to prevent SQL injection
query = "SELECT id, username, email, role FROM users WHERE username = ? AND password_hash = ?"
cursor.execute(query, (username, password_hash))

row = cursor.fetchone()
return dict(row) if row else None

except sqlite3.Error as e:
print(f"Database error: {e}")
return None
finally:
conn.close()

def create_user(self, user_data: Dict[str, Any]) -> bool:
"""Secure user creation with input validation"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()

try:
query = """
INSERT INTO users (username, email, password_hash, role)
VALUES (?, ?, ?, ?)
"""
cursor.execute(query, (
user_data['username'],
user_data['email'],
user_data['password_hash'],
user_data.get('role', 'user')
))

conn.commit()
return True

except sqlite3.IntegrityError:
print("User already exists")
return False
except sqlite3.Error as e:
print(f"Database error: {e}")
return False
finally:
conn.close()

NoSQL Injection Prevention:

// MongoDB injection vulnerability
app.post('/login', async (req, res) => {
const { username, password } = req.body;

// VULNERABLE: Direct object injection
const user = await User.findOne({
username: username,
password: password
});

// Attack payload: {"username": {"$ne": ""}, "password": {"$ne": ""}}
// This would match any user where username and password are not empty
});

// Secure implementation
const Joi = require('joi');

const loginSchema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
password: Joi.string().min(8).required()
});

app.post('/login', async (req, res) => {
// Validate input
const { error, value } = loginSchema.validate(req.body);
if (error) {
return res.status(400).json({ error: 'Invalid input' });
}

const { username, password } = value;

try {
// Type checking to prevent object injection
if (typeof username !== 'string' || typeof password !== 'string') {
return res.status(400).json({ error: 'Invalid input format' });
}

const user = await User.findOne({ username: username });

if (!user || !await bcrypt.compare(password, user.passwordHash)) {
return res.status(401).json({ error: 'Invalid credentials' });
}

// Generate JWT token
const token = generateToken(user);
res.json({ token });

} catch (error) {
console.error('Login error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});

A04:2021 – Insecure Design​

Risk: Missing or ineffective control design

Example: Insecure Password Recovery:

// INSECURE: Predictable reset tokens
app.post('/forgot-password', async (req, res) => {
const { email } = req.body;
const user = await User.findOne({ email });

if (!user) {
return res.json({ message: 'Reset email sent if account exists' });
}

// BAD: Predictable token
const resetToken = user.id + '_' + Date.now();

await user.update({ resetToken });

// Send email with reset link
await sendResetEmail(email, resetToken);

res.json({ message: 'Reset email sent if account exists' });
});

Secure Design Implementation:

const crypto = require('crypto');

class PasswordResetService {
constructor() {
this.resetTokens = new Map(); // In production, use Redis
}

async initiateReset(email) {
const user = await User.findOne({ email });

// Always return the same response to prevent user enumeration
const response = { message: 'Reset email sent if account exists' };

if (!user) {
// Simulate processing time to prevent timing attacks
await new Promise(resolve => setTimeout(resolve, 100));
return response;
}

// Generate cryptographically secure token
const resetToken = crypto.randomBytes(32).toString('hex');
const expiresAt = new Date(Date.now() + 15 * 60 * 1000); // 15 minutes

// Store token with expiration
this.resetTokens.set(resetToken, {
userId: user.id,
email: user.email,
expiresAt,
attempts: 0
});

// Send secure reset email
await this.sendResetEmail(email, resetToken);

return response;
}

async validateResetToken(token) {
const tokenData = this.resetTokens.get(token);

if (!tokenData) {
throw new Error('Invalid or expired reset token');
}

if (new Date() > tokenData.expiresAt) {
this.resetTokens.delete(token);
throw new Error('Reset token has expired');
}

// Implement rate limiting for token validation
tokenData.attempts += 1;
if (tokenData.attempts > 3) {
this.resetTokens.delete(token);
throw new Error('Too many attempts');
}

return tokenData;
}

async resetPassword(token, newPassword) {
const tokenData = await this.validateResetToken(token);

// Validate password strength
if (!this.isPasswordStrong(newPassword)) {
throw new Error('Password does not meet security requirements');
}

// Hash new password
const passwordHash = await bcrypt.hash(newPassword, 12);

// Update user password
await User.updateOne(
{ id: tokenData.userId },
{
passwordHash,
passwordResetAt: new Date(),
// Invalidate all existing sessions
sessionVersion: crypto.randomBytes(16).toString('hex')
}
);

// Clean up used token
this.resetTokens.delete(token);

// Log security event
this.logSecurityEvent('PASSWORD_RESET', {
userId: tokenData.userId,
email: tokenData.email
});
}

isPasswordStrong(password) {
const requirements = [
password.length >= 12,
/[a-z]/.test(password),
/[A-Z]/.test(password),
/[0-9]/.test(password),
/[^A-Za-z0-9]/.test(password)
];

return requirements.every(req => req);
}
}

A05:2021 – Security Misconfiguration​

Risk: Improperly configured security settings

Common Misconfigurations:

// BAD: Debug mode in production
if (process.env.NODE_ENV === 'production') {
app.set('debug', true); // Should be false!
}

// BAD: Default credentials
const defaultConfig = {
adminUsername: 'admin',
adminPassword: 'admin123' // Never use defaults!
};

// BAD: Overly permissive CORS
app.use(cors({
origin: '*', // Allows any origin
credentials: true // Dangerous with wildcard origin
}));

// BAD: Exposing sensitive information
app.use((err, req, res, next) => {
res.status(500).json({
error: err.message,
stack: err.stack // Exposes stack trace in production
});
});

Secure Configuration:

const config = require('./config');

// Environment-specific configuration
const secureConfig = {
development: {
debug: true,
cors: {
origin: ['http://localhost:3000', 'http://localhost:3001'],
credentials: true
},
logging: 'verbose'
},

production: {
debug: false,
cors: {
origin: ['https://myapp.com', 'https://www.myapp.com'],
credentials: true,
optionsSuccessStatus: 200
},
logging: 'error',
helmet: {
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}
}
};

const currentConfig = secureConfig[process.env.NODE_ENV] || secureConfig.production;

// Apply security middleware
app.use(helmet(currentConfig.helmet));
app.use(cors(currentConfig.cors));

// Secure error handling
app.use((err, req, res, next) => {
// Log full error details
console.error('Error occurred:', {
message: err.message,
stack: err.stack,
url: req.url,
method: req.method,
ip: req.ip,
userAgent: req.get('User-Agent')
});

// Return generic error in production
const isDevelopment = process.env.NODE_ENV === 'development';

res.status(err.status || 500).json({
error: isDevelopment ? err.message : 'Internal server error',
...(isDevelopment && { stack: err.stack })
});
});

// Security headers middleware
app.use((req, res, next) => {
// Remove server information
res.removeHeader('X-Powered-By');

// Set security headers
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');

next();
});

A06:2021 – Vulnerable and Outdated Components​

Risk: Using components with known vulnerabilities

Vulnerable Dependencies Management:

{
"scripts": {
"audit": "npm audit",
"audit-fix": "npm audit fix",
"outdated": "npm outdated",
"security-check": "npm audit --audit-level moderate"
},
"dependencies": {
"express": "^4.18.0",
"lodash": "^4.17.21"
}
}

Automated Security Scanning:

# .github/workflows/security.yml
name: Security Audit

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 0 * * 0' # Weekly

jobs:
security-audit:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run security audit
run: |
npm audit --audit-level moderate
npx audit-ci --moderate

- name: Snyk security scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=medium

- name: Upload Snyk results
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: snyk.sarif

A07:2021 – Identification and Authentication Failures​

Risk: Compromised passwords, keys, or session tokens

Weak Authentication Implementation:

// BAD: Weak session management
app.post('/login', async (req, res) => {
const { username, password } = req.body;

if (username === 'admin' && password === 'password') {
req.session.user = username; // No session security
res.json({ success: true });
}
});

// BAD: No brute force protection
app.post('/login', async (req, res) => {
const user = await User.findOne({ username: req.body.username });

if (user && user.password === req.body.password) { // Plain text passwords!
res.json({ token: user.id }); // Predictable tokens
}
});

Secure Authentication System:

const bcrypt = require('bcrypt');
const rateLimit = require('express-rate-limit');
const MongoStore = require('connect-mongo');

// Rate limiting for login attempts
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // limit each IP to 5 requests per windowMs
skipSuccessfulRequests: true,
message: {
error: 'Too many login attempts, please try again later'
}
});

// Account lockout after failed attempts
class AuthenticationService {
static async authenticateUser(username, password) {
const user = await User.findOne({ username });

if (!user) {
// Simulate processing time to prevent username enumeration
await bcrypt.hash('dummy', 12);
throw new Error('Invalid credentials');
}

// Check if account is locked
if (user.lockoutExpires && user.lockoutExpires > new Date()) {
throw new Error('Account is temporarily locked');
}

const isValidPassword = await bcrypt.compare(password, user.passwordHash);

if (!isValidPassword) {
await this.handleFailedAttempt(user);
throw new Error('Invalid credentials');
}

// Reset failed attempts on successful login
await user.updateOne({
$unset: { failedAttempts: 1, lockoutExpires: 1 }
});

return user;
}

static async handleFailedAttempt(user) {
const failedAttempts = (user.failedAttempts || 0) + 1;
const updates = { failedAttempts };

// Lock account after 5 failed attempts
if (failedAttempts >= 5) {
updates.lockoutExpires = new Date(Date.now() + 30 * 60 * 1000); // 30 minutes
}

await user.updateOne(updates);
}
}

// Secure session configuration
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
store: MongoStore.create({
mongoUrl: process.env.MONGODB_URI,
ttl: 24 * 60 * 60 // 24 hours
}),
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000, // 24 hours
sameSite: 'strict'
}
}));

// Secure login endpoint
app.post('/login', loginLimiter, async (req, res) => {
const { username, password } = req.body;

try {
// Input validation
if (!username || !password) {
return res.status(400).json({ error: 'Username and password required' });
}

const user = await AuthenticationService.authenticateUser(username, password);

// Create secure session
req.session.userId = user.id;
req.session.username = user.username;
req.session.role = user.role;

// Log successful login
await AuditLog.create({
action: 'LOGIN_SUCCESS',
userId: user.id,
ip: req.ip,
userAgent: req.get('User-Agent')
});

res.json({
success: true,
user: {
id: user.id,
username: user.username,
role: user.role
}
});

} catch (error) {
// Log failed attempt
await AuditLog.create({
action: 'LOGIN_FAILURE',
username: username,
ip: req.ip,
userAgent: req.get('User-Agent'),
error: error.message
});

res.status(401).json({ error: 'Invalid credentials' });
}
});

A08:2021 – Software and Data Integrity Failures​

Risk: Code and infrastructure that does not protect against integrity violations

Subresource Integrity (SRI):

<!-- Use SRI for external resources -->
<script
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"
integrity="sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GlLJvkbp6oJ8"
crossorigin="anonymous">
</script>

<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
crossorigin="anonymous">

Digital Signatures for Updates:

const crypto = require('crypto');
const fs = require('fs');

class UpdateVerifier {
constructor(publicKeyPath) {
this.publicKey = fs.readFileSync(publicKeyPath, 'utf8');
}

verifyUpdate(updatePath, signaturePath) {
try {
const updateData = fs.readFileSync(updatePath);
const signature = fs.readFileSync(signaturePath);

const verifier = crypto.createVerify('SHA256');
verifier.update(updateData);

const isValid = verifier.verify(this.publicKey, signature);

if (!isValid) {
throw new Error('Update signature verification failed');
}

return true;
} catch (error) {
console.error('Update verification failed:', error);
return false;
}
}

calculateHash(filePath) {
const fileBuffer = fs.readFileSync(filePath);
const hash = crypto.createHash('sha256');
hash.update(fileBuffer);
return hash.digest('hex');
}
}

// CI/CD pipeline integrity
const pipelineIntegrity = {
stages: [
{
name: 'source-verification',
checks: [
'verify-git-signature',
'scan-dependencies',
'check-code-signatures'
]
},
{
name: 'build-verification',
checks: [
'reproducible-build',
'artifact-signing',
'vulnerability-scan'
]
},
{
name: 'deployment-verification',
checks: [
'verify-artifact-signature',
'infrastructure-validation',
'runtime-integrity-check'
]
}
]
};

A09:2021 – Security Logging and Monitoring Failures​

Risk: Insufficient logging and monitoring of security events

Comprehensive Security Logging:

const winston = require('winston');

class SecurityLogger {
constructor() {
this.logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.File({
filename: 'logs/security-events.log',
level: 'warn'
}),
new winston.transports.File({
filename: 'logs/audit.log'
}),
...(process.env.NODE_ENV !== 'production' ? [
new winston.transports.Console()
] : [])
]
});
}

logSecurityEvent(event, details) {
const logEntry = {
timestamp: new Date().toISOString(),
event: event,
severity: this.getSeverity(event),
details: {
...details,
userAgent: details.userAgent,
ip: details.ip,
sessionId: details.sessionId
}
};

this.logger.warn('Security Event', logEntry);

// Send to SIEM if critical
if (logEntry.severity === 'CRITICAL') {
this.sendToSIEM(logEntry);
}
}

getSeverity(event) {
const severityMap = {
'LOGIN_FAILURE': 'LOW',
'MULTIPLE_LOGIN_FAILURES': 'MEDIUM',
'ACCOUNT_LOCKED': 'MEDIUM',
'PRIVILEGE_ESCALATION_ATTEMPT': 'HIGH',
'DATA_BREACH_ATTEMPT': 'CRITICAL',
'INJECTION_ATTEMPT': 'HIGH',
'SUSPICIOUS_FILE_ACCESS': 'MEDIUM'
};

return severityMap[event] || 'LOW';
}

async sendToSIEM(logEntry) {
// Integration with SIEM system
try {
// Example: Send to Splunk, ELK, or other SIEM
await fetch(process.env.SIEM_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.SIEM_TOKEN}`
},
body: JSON.stringify(logEntry)
});
} catch (error) {
console.error('Failed to send to SIEM:', error);
}
}
}

// Security monitoring middleware
const securityLogger = new SecurityLogger();

app.use((req, res, next) => {
// Log suspicious patterns
const suspiciousPatterns = [
/\.\./g, // Directory traversal
/(union|select|insert|update|delete|drop)/i, // SQL injection
/<script/i, // XSS attempts
/javascript:/i // JavaScript injection
];

const isSuspicious = suspiciousPatterns.some(pattern =>
pattern.test(req.url) || pattern.test(JSON.stringify(req.body))
);

if (isSuspicious) {
securityLogger.logSecurityEvent('SUSPICIOUS_REQUEST', {
url: req.url,
method: req.method,
body: req.body,
ip: req.ip,
userAgent: req.get('User-Agent')
});
}

next();
});

A10:2021 – Server-Side Request Forgery (SSRF)​

Risk: Fetching remote resources without validating user-supplied URLs

Vulnerable SSRF Code:

// DANGEROUS: No URL validation
app.post('/fetch-url', async (req, res) => {
const { url } = req.body;

try {
const response = await fetch(url); // Can access internal services!
const data = await response.text();
res.json({ data });
} catch (error) {
res.status(500).json({ error: error.message });
}
});

// Attack examples:
// url: "http://localhost:22" (SSH service)
// url: "http://169.254.169.254/latest/meta-data/" (AWS metadata)
// url: "file:///etc/passwd" (Local file access)

Secure SSRF Prevention:

const url = require('url');
const dns = require('dns');
const { promisify } = require('util');

const dnsLookup = promisify(dns.lookup);

class SSRFProtection {
constructor() {
// Whitelist of allowed domains
this.allowedDomains = [
'api.trusted-service.com',
'cdn.allowed-content.com'
];

// Blacklist of dangerous IP ranges
this.blockedNetworks = [
'127.0.0.0/8', // Loopback
'10.0.0.0/8', // Private network
'172.16.0.0/12', // Private network
'192.168.0.0/16', // Private network
'169.254.0.0/16', // Link-local
'0.0.0.0/8', // Current network
'224.0.0.0/4', // Multicast
'240.0.0.0/4' // Reserved
];
}

async validateURL(inputURL) {
try {
const parsedURL = new url.URL(inputURL);

// Only allow HTTP/HTTPS
if (!['http:', 'https:'].includes(parsedURL.protocol)) {
throw new Error('Only HTTP/HTTPS protocols allowed');
}

// Check if domain is whitelisted
if (!this.allowedDomains.includes(parsedURL.hostname)) {
throw new Error('Domain not in whitelist');
}

// Resolve hostname to IP
const { address } = await dnsLookup(parsedURL.hostname);

// Check if IP is in blocked networks
if (this.isIPBlocked(address)) {
throw new Error('Access to internal networks not allowed');
}

return { isValid: true, resolvedIP: address };

} catch (error) {
return { isValid: false, error: error.message };
}
}

isIPBlocked(ip) {
// Simple IP range checking (use a proper library in production)
const ipParts = ip.split('.').map(Number);

// Check loopback
if (ipParts[0] === 127) return true;

// Check private networks
if (ipParts[0] === 10) return true;
if (ipParts[0] === 172 && ipParts[1] >= 16 && ipParts[1] <= 31) return true;
if (ipParts[0] === 192 && ipParts[1] === 168) return true;

// Check link-local
if (ipParts[0] === 169 && ipParts[1] === 254) return true;

return false;
}

async safeFetch(inputURL, options = {}) {
const validation = await this.validateURL(inputURL);

if (!validation.isValid) {
throw new Error(`SSRF protection: ${validation.error}`);
}

// Additional safety measures
const safeOptions = {
...options,
timeout: 10000, // 10 second timeout
size: 1024 * 1024, // 1MB max response size
redirect: 'manual' // Don't follow redirects
};

const response = await fetch(inputURL, safeOptions);

// Check for suspicious redirects
if (response.status >= 300 && response.status < 400) {
const location = response.headers.get('location');
if (location) {
const redirectValidation = await this.validateURL(location);
if (!redirectValidation.isValid) {
throw new Error('Redirect blocked by SSRF protection');
}
}
}

return response;
}
}

// Secure URL fetching endpoint
const ssrfProtection = new SSRFProtection();

app.post('/fetch-url', async (req, res) => {
const { url } = req.body;

// Input validation
if (!url || typeof url !== 'string') {
return res.status(400).json({ error: 'Valid URL required' });
}

try {
const response = await ssrfProtection.safeFetch(url);
const data = await response.text();

// Log the request for monitoring
securityLogger.logSecurityEvent('EXTERNAL_URL_FETCH', {
url: url,
ip: req.ip,
userAgent: req.get('User-Agent'),
userId: req.user?.id
});

res.json({ data });

} catch (error) {
// Log potential SSRF attempt
securityLogger.logSecurityEvent('SSRF_ATTEMPT_BLOCKED', {
url: url,
error: error.message,
ip: req.ip,
userAgent: req.get('User-Agent')
});

res.status(400).json({ error: 'Request blocked for security reasons' });
}
});

Security Testing and Validation​

// Security test suite
describe('OWASP Top 10 Security Tests', () => {
test('A01 - Access Control', async () => {
// Test unauthorized access
const response = await request(app)
.get('/admin/users')
.expect(401);

expect(response.body.error).toBe('Authentication required');
});

test('A03 - Injection Prevention', async () => {
const sqlInjectionPayload = "'; DROP TABLE users; --";

const response = await request(app)
.post('/login')
.send({
username: sqlInjectionPayload,
password: 'password'
})
.expect(401);

// Should not cause database error
expect(response.body.error).toBe('Invalid credentials');
});

test('A06 - Dependency Security', () => {
const auditResult = execSync('npm audit --audit-level moderate', {
encoding: 'utf8'
});

expect(auditResult).not.toContain('found 0 vulnerabilities');
});
});

The OWASP Top 10 provides a foundation for web application security. Regularly review and implement these protections, conduct security testing, and stay updated with the latest security trends and vulnerabilities.

Content Review​

The content in this repository has been reviewed by chevp. Chevp is dedicated to ensuring that the information provided is accurate, relevant, and up-to-date, helping users to learn and implement programming skills effectively.

About the Reviewer​

For more insights and contributions, visit chevp's GitHub profile: chevp's GitHub Profile.