10 KiB
10 KiB
Vulnerability Remediation Guide
Security remediation patterns organized by vulnerability category.
Table of Contents
- SQL Injection
- Cross-Site Scripting (XSS)
- Command Injection
- Path Traversal
- Insecure Deserialization
- Weak Cryptography
- Authentication & Session Management
- CSRF
- SSRF
- XXE
SQL Injection
Vulnerability Pattern
# VULNERABLE
query = f"SELECT * FROM users WHERE id = {user_id}"
cursor.execute(query)
Secure Remediation
# SECURE: Use parameterized queries
query = "SELECT * FROM users WHERE id = %s"
cursor.execute(query, (user_id,))
# Or use ORM
user = User.objects.get(id=user_id)
Framework-Specific Solutions
Django:
# Use Django ORM (safe by default)
User.objects.filter(email=user_email)
# For raw SQL, use parameterized queries
User.objects.raw('SELECT * FROM myapp_user WHERE email = %s', [user_email])
Node.js (Sequelize):
// Use parameterized queries
User.findAll({
where: { email: userEmail }
});
// Or use replacements
sequelize.query(
'SELECT * FROM users WHERE email = :email',
{ replacements: { email: userEmail } }
);
Java (JDBC):
// Use PreparedStatement
String query = "SELECT * FROM users WHERE id = ?";
PreparedStatement stmt = conn.prepareStatement(query);
stmt.setInt(1, userId);
ResultSet rs = stmt.executeQuery();
Cross-Site Scripting (XSS)
Vulnerability Pattern
// VULNERABLE
element.innerHTML = userInput;
document.write(userInput);
Secure Remediation
// SECURE: Use textContent for text
element.textContent = userInput;
// Or properly escape HTML
element.innerHTML = escapeHtml(userInput);
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
Framework-Specific Solutions
React:
// React auto-escapes by default
<div>{userInput}</div>
// For HTML content, sanitize first
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(userInput)}} />
Flask/Jinja2:
# Templates auto-escape by default
{{ user_input }}
# For HTML content, sanitize
from markupsafe import Markup
import bleach
{{ Markup(bleach.clean(user_input)) }}
Django:
{# Auto-escaped by default #}
{{ user_input }}
{# Mark as safe only after sanitization #}
{{ user_input|safe }}
Command Injection
Vulnerability Pattern
# VULNERABLE
os.system(f"ping {user_host}")
subprocess.call(f"ls {user_directory}", shell=True)
Secure Remediation
# SECURE: Use subprocess with list arguments
import subprocess
subprocess.run(['ping', '-c', '1', user_host],
capture_output=True, check=True)
# Validate input against allowlist
import shlex
if not re.match(r'^[a-zA-Z0-9.-]+$', user_host):
raise ValueError("Invalid hostname")
subprocess.run(['ping', '-c', '1', user_host])
Node.js:
// VULNERABLE
exec(`ls ${userDir}`);
// SECURE
const { execFile } = require('child_process');
execFile('ls', [userDir], (error, stdout) => {
// Handle output
});
Path Traversal
Vulnerability Pattern
# VULNERABLE
file_path = os.path.join('/uploads', user_filename)
with open(file_path) as f:
return f.read()
Secure Remediation
# SECURE: Validate and normalize path
import os
from pathlib import Path
def safe_join(directory, user_path):
# Normalize and resolve path
base_dir = Path(directory).resolve()
file_path = (base_dir / user_path).resolve()
# Ensure it's within base directory
if not str(file_path).startswith(str(base_dir)):
raise ValueError("Path traversal detected")
return file_path
try:
safe_path = safe_join('/uploads', user_filename)
with open(safe_path) as f:
return f.read()
except ValueError:
return "Invalid filename"
Insecure Deserialization
Vulnerability Pattern
# VULNERABLE
import pickle
data = pickle.loads(user_data)
Secure Remediation
# SECURE: Use safe formats like JSON
import json
data = json.loads(user_data)
# If you must deserialize, validate and restrict
import yaml
data = yaml.safe_load(user_data) # Use safe_load, not load
Node.js:
// VULNERABLE
const data = eval(userInput);
const obj = Function(userInput)();
// SECURE
const data = JSON.parse(userInput);
// For complex objects, use schema validation
const Joi = require('joi');
const schema = Joi.object({
name: Joi.string().required(),
email: Joi.string().email().required()
});
const { value, error } = schema.validate(JSON.parse(userInput));
Weak Cryptography
Vulnerability Pattern
# VULNERABLE
import hashlib
password_hash = hashlib.md5(password.encode()).hexdigest()
Secure Remediation
# SECURE: Use bcrypt or argon2
import bcrypt
# Hashing
password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
# Verification
if bcrypt.checkpw(password.encode(), stored_hash):
print("Password correct")
# Or use argon2
from argon2 import PasswordHasher
ph = PasswordHasher()
hash = ph.hash(password)
ph.verify(hash, password)
Encryption:
# VULNERABLE
from Crypto.Cipher import DES
cipher = DES.new(key, DES.MODE_ECB)
# SECURE: Use AES-GCM
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
key = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(key)
nonce = os.urandom(12)
ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data)
Authentication & Session Management
Vulnerability Pattern
// VULNERABLE
app.use(session({
secret: 'weak-secret',
cookie: { secure: false }
}));
Secure Remediation
// SECURE
const session = require('express-session');
app.use(session({
secret: process.env.SESSION_SECRET, // Strong random secret
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS only
httpOnly: true, // No JavaScript access
sameSite: 'strict', // CSRF protection
maxAge: 3600000 // 1 hour
}
}));
Password Requirements:
# Implement strong password policy
import re
def validate_password(password):
if len(password) < 12:
return False
if not re.search(r'[A-Z]', password):
return False
if not re.search(r'[a-z]', password):
return False
if not re.search(r'[0-9]', password):
return False
if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
return False
return True
CSRF
Vulnerability Pattern
# VULNERABLE: No CSRF protection
@app.route('/transfer', methods=['POST'])
def transfer():
amount = request.form['amount']
to_account = request.form['to']
# Process transfer
Secure Remediation
# SECURE: Use CSRF tokens
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)
@app.route('/transfer', methods=['POST'])
@csrf.exempt # Only if using custom CSRF
def transfer():
# CSRF token automatically validated
amount = request.form['amount']
to_account = request.form['to']
Express.js:
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.post('/transfer', csrfProtection, (req, res) => {
// CSRF token validated
const { amount, to } = req.body;
});
SSRF
Vulnerability Pattern
# VULNERABLE
import requests
url = request.args.get('url')
response = requests.get(url)
Secure Remediation
# SECURE: Validate URLs and use allowlist
import requests
from urllib.parse import urlparse
ALLOWED_DOMAINS = ['api.example.com', 'cdn.example.com']
def safe_fetch(url):
parsed = urlparse(url)
# Check protocol
if parsed.scheme not in ['http', 'https']:
raise ValueError("Invalid protocol")
# Check domain against allowlist
if parsed.netloc not in ALLOWED_DOMAINS:
raise ValueError("Domain not allowed")
# Block internal IPs
import ipaddress
try:
ip = ipaddress.ip_address(parsed.hostname)
if ip.is_private:
raise ValueError("Private IP not allowed")
except ValueError:
pass # Not an IP, continue
return requests.get(url, timeout=5)
XXE
Vulnerability Pattern
# VULNERABLE
from lxml import etree
tree = etree.parse(user_xml)
Secure Remediation
# SECURE: Disable external entities
from lxml import etree
parser = etree.XMLParser(
resolve_entities=False,
no_network=True,
dtd_validation=False
)
tree = etree.parse(user_xml, parser)
# Or use defusedxml
from defusedxml import ElementTree
tree = ElementTree.parse(user_xml)
Node.js:
// Use secure XML parser
const libxmljs = require('libxmljs');
const xml = libxmljs.parseXml(userXml, {
noent: false, // Disable entity expansion
dtdload: false,
dtdvalid: false
});
General Security Principles
- Input Validation: Validate all user input against expected format
- Output Encoding: Encode output based on context (HTML, URL, SQL, etc.)
- Least Privilege: Grant minimum necessary permissions
- Defense in Depth: Use multiple layers of security controls
- Fail Securely: Ensure failures don't expose sensitive data
- Secure Defaults: Use secure configuration by default
- Keep Dependencies Updated: Regularly update libraries and frameworks
Testing Remediation
After applying fixes:
-
Verify with Semgrep: Re-scan to ensure vulnerability is resolved
semgrep --config <ruleset> fixed_file.py -
Manual Testing: Attempt to exploit the vulnerability
-
Code Review: Have peer review the fix
-
Integration Tests: Add tests to prevent regression