OWASP Top 10 for Python: Complete Overview
The OWASP Top 10 is a regularly updated list of the 10 most critical security risks in web applications, compiled and maintained by the Open Worldwide Application Security Project. The list is based on analysis of real-world breaches, security assessments, and vulnerability databases. Every Python developer should understand these categories, how they manifest in Python code, and how to defend against them. The 2021 edition reorganized items based on frequency and impact; the list is more aligned with attack patterns than with the previous 2017 edition.
The OWASP Top 10 (2021 Edition)
| Rank | Vulnerability | Python Examples | Primary Defense |
|---|---|---|---|
| 1 | Broken Access Control | Missing authorization checks, privilege escalation, missing object-level controls | Use RBAC/ABAC, verify permissions on every action, test access controls |
| 2 | Cryptographic Failures | Plaintext passwords, unencrypted HTTPS, weak encryption algorithms | Use bcrypt for passwords, enforce HTTPS, use AES-256 for encryption |
| 3 | Injection | SQL injection, command injection, LDAP injection | Parameterized queries, input validation, ORM (SQLAlchemy) |
| 4 | Insecure Design | Missing threat models, insecure authentication flow, no rate limiting | Threat modeling, secure design patterns, architecture review |
| 5 | Security Misconfiguration | Debug mode on in production, exposed admin panels, default credentials | Infrastructure-as-Code, automated security testing, secrets management |
| 6 | Vulnerable and Outdated Components | Unpatched libraries, transitive dependencies with known CVEs | Dependency scanning with pip-audit, Dependabot, version pinning |
| 7 | Authentication and Session Failures | Weak sessions, credential enumeration, session fixation | Use bcrypt, JWT, MFA, secure session management |
| 8 | Software and Data Integrity Failures | Unsafe deserialization, unsigned updates, tampered with objects | Use JSON instead of pickle, verify signatures, code signing |
| 9 | Logging and Monitoring Failures | Missing audit logs, no alerting, slow incident detection | Structured logging, centralized logging, alerting on anomalies |
| 10 | Server-Side Request Forgery (SSRF) | Internal network scanning, cloud metadata leaks, blind SSRF | Whitelist allowed URLs, disable dangerous protocols, use firewalls |
The order reflects the frequency of attacks and the severity of impact. Broken Access Control is #1 not because it is the most dangerous in theory, but because it is the most commonly exploited in real-world attacks.
Broken Access Control: The Most Common Vulnerability
Broken access control occurs when applications fail to verify that users are authorized to access resources. A Flask API that returns user data without checking if the requesting user owns that data is vulnerable:
# VULNERABLE — no authorization check
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/api/users/<int:user_id>')
def get_user(user_id: int):
# Retrieve and return the user without checking if the requester owns this data
user = db.query(User).filter(User.id == user_id).first()
return jsonify(user.to_dict())
# Attacker can access /api/users/999 and retrieve anyone's data
The fix is to verify authorization before returning data:
# SECURE — authorization check
@app.route('/api/users/<int:user_id>')
def get_user(user_id: int):
# Get the authenticated user from JWT or session
current_user_id = get_current_user()
# Only allow users to view their own data (or admins to view anyone's)
if current_user_id != user_id and not is_admin(current_user_id):
return jsonify({'error': 'Forbidden'}), 403
user = db.query(User).filter(User.id == user_id).first()
return jsonify(user.to_dict())
Every endpoint that accesses user data, files, or configuration must explicitly verify authorization. Use a decorator to enforce this consistently:
def require_ownership(resource_type: str):
"""Decorator to verify user owns the requested resource."""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
resource_id = kwargs.get('resource_id')
current_user_id = get_current_user()
# Check ownership
resource = db.query(resource_type).filter(
resource_type.id == resource_id,
resource_type.user_id == current_user_id,
).first()
if not resource:
return jsonify({'error': 'Not found or forbidden'}), 403
return f(*args, **kwargs)
return decorated_function
return decorator
@app.route('/api/posts/<int:post_id>', methods=['DELETE'])
@require_ownership(Post)
def delete_post(post_id: int):
post = db.query(Post).filter(Post.id == post_id).first()
db.delete(post)
db.commit()
return jsonify({'message': 'Post deleted'})
Injection (SQL, Command, LDAP)
Injection occurs when untrusted input is concatenated into commands or queries that are executed by a backend system. The defense is parameterization—separating the command structure from the data:
# SQL Injection (VULNERABLE)
query = f"SELECT * FROM users WHERE email = '{user_email}'"
# SQL Injection (SECURE)
cursor.execute("SELECT * FROM users WHERE email = ?", (user_email,))
# Command Injection (VULNERABLE)
os.system(f"rm -rf /tmp/{filename}")
# Command Injection (SECURE — avoid shell=True)
subprocess.run(['rm', '-rf', f'/tmp/{filename}'], shell=False, capture_output=True)
# LDAP Injection (VULNERABLE)
ldap_filter = f"(uid={username})"
# LDAP Injection (SECURE — use ldap3 library with escaping)
from ldap3 import escape_filter_chars
ldap_filter = f"(uid={escape_filter_chars(username)})"
The common theme: never concatenate user input into command strings or queries. Always use parameterized/prepared statements or escaping functions.
Insecure Deserialization and Vulnerable Components
Deserialization vulnerabilities occur when pickle.loads() is called on untrusted data. Vulnerable components are outdated libraries with known CVEs. Both are addressed through the defenses discussed in earlier articles: use JSON instead of pickle, and scan dependencies with pip-audit.
Broken Authentication and Session Failures
Weak authentication allows attackers to guess or brute-force credentials. Session fixation allows attackers to hijack sessions. Defenses:
- Use bcrypt (or Argon2) for password hashing with high cost factors.
- Implement rate limiting on login endpoints to prevent brute-force.
- Use secure session management (HTTP-only cookies, same-site attributes).
- Implement multi-factor authentication (TOTP or FIDO2).
- Invalidate sessions on logout.
Cryptographic Failures
Storing passwords in plaintext, transmitting data over HTTP, or using weak encryption allows attackers to steal sensitive data. Defenses:
- Always enforce HTTPS (configure
Strict-Transport-Securityheaders). - Hash passwords with bcrypt, not MD5 or SHA-1.
- Use AES-256-GCM for encryption at rest.
- Encrypt sensitive data (PII, payment data) in the database.
Insecure Design and Security Misconfiguration
Insecure design flaws exist at the architectural level; they cannot be patched. Examples include missing threat models, no rate limiting, and predictable password reset tokens. Security misconfiguration is simpler: debug mode on in production, hardcoded credentials, or default credentials on admin panels.
Defend against misconfiguration with infrastructure-as-code (Terraform, CloudFormation) and automated security testing:
# Check for common misconfigurations
def verify_production_settings(settings):
assert not settings.DEBUG, "Debug mode must be off in production"
assert settings.SECRET_KEY != 'change-me', "Change SECRET_KEY in production"
assert settings.ALLOWED_HOSTS != ['*'], "Restrict ALLOWED_HOSTS in production"
assert 'https' in settings.CSRF_TRUSTED_ORIGINS, "Use HTTPS for CSRF origins"
Logging, Monitoring, and SSRF
Without logging and monitoring, you cannot detect attacks. Log all authentication attempts, data access, and configuration changes. Monitor for anomalies (bulk data exports, repeated login failures).
Server-Side Request Forgery (SSRF) occurs when your application makes requests to URLs controlled by attackers, which can be abused to:
- Scan internal networks.
- Access cloud metadata (AWS EC2 instance metadata leaks credentials).
- Perform blind SSRF attacks (exfiltrate data via DNS or timing).
Defense: whitelist allowed URLs and disable dangerous protocols (file://, dict://, gopher://):
from urllib.parse import urlparse
import socket
def safe_request(url: str) -> str:
"""Fetch a URL safely, preventing SSRF."""
# Whitelist allowed domains
allowed_domains = ['example.com', 'api.example.com']
parsed = urlparse(url)
# Reject dangerous protocols
if parsed.scheme not in ['http', 'https']:
raise ValueError(f"Unsupported protocol: {parsed.scheme}")
# Reject private IP addresses
hostname = parsed.hostname
try:
ip = socket.gethostbyname(hostname)
if ip.startswith(('10.', '192.168.', '127.', '172.')):
raise ValueError(f"Private IP address not allowed: {ip}")
except socket.gaierror:
raise ValueError(f"Could not resolve hostname: {hostname}")
# Reject unlisted domains
if hostname not in allowed_domains:
raise ValueError(f"Domain not in whitelist: {hostname}")
return requests.get(url).text
Key Takeaways
- The OWASP Top 10 reflects the most common and impactful web application vulnerabilities; every Python developer must understand all 10 categories.
- Broken access control (#1) is the most frequently exploited; always verify authorization before returning data or performing actions.
- Injection attacks (#3) are prevented by parameterized queries and input validation.
- Vulnerable components (#6) are mitigated by continuous dependency scanning with
pip-audit. - Cryptographic failures (#2) require HTTPS enforcement, bcrypt password hashing, and AES encryption for sensitive data.
Frequently Asked Questions
Has the OWASP Top 10 changed recently?
Yes. The 2021 edition reorganized items based on frequency and impact. Insecure Deserialization was #8 in 2017; it dropped out of the top 10 in 2021 (but remains critical). The 2024 edition may reflect current trends; check the official OWASP site for updates.
Do all 10 vulnerabilities apply to my application?
Not necessarily. SSRF is more common in applications that fetch external URLs; e.g., email scrapers, RSS readers. Focus on the vulnerabilities that are most relevant to your threat model.
Should I memorize the OWASP Top 10?
No. Understand the concepts (injection, access control, cryptography); remember the list as a reference. Tools like OWASP ZAP can automatically scan for many of these vulnerabilities.
Is there a Python OWASP Top 10?
The OWASP Top 10 is language-agnostic; it applies to Python, Java, .NET, etc. However, the specific defenses differ. This series focuses on Python-specific implementations.
How do I know if my application is vulnerable?
Conduct a threat model (identify assets and attack paths), perform code review, use automated scanning tools (OWASP ZAP, Snyk), and conduct a penetration test.