Skip to main content
โšก Calmops

Web Security: Complete Guide to Common Vulnerabilities and Prevention

Introduction

Web applications face constant threats from attackers seeking to steal data, disrupt services, or gain unauthorized access. Understanding common vulnerabilities is the first step to building secure applications. This comprehensive guide covers the most critical web security issues and provides practical prevention techniques.

Security must be built into applications from the ground up, not added as an afterthought. With cyberattacks becoming more sophisticated and the cost of breaches reaching millions of dollars, understanding web security is essential for every developer.

This guide covers the OWASP Top 10, specific vulnerability types, and practical defense mechanisms you can implement immediately.

OWASP Top 10 (2026)

The Open Web Application Security Project (OWASP) maintains a list of the most critical web application security risks:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    OWASP Top 10 (2026)                                   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                       โ”‚
โ”‚  1. Broken Access Control         - Unauthorized data access         โ”‚
โ”‚  2. Cryptographic Failures         - Weak encryption                 โ”‚
โ”‚  3. Injection                      - SQL, NoSQL, Command injection   โ”‚
โ”‚  4. Insecure Design                 - Missing security patterns       โ”‚
โ”‚  5. Security Misconfiguration       - Improper setup                  โ”‚
โ”‚  6. Vulnerable Components           - Outdated dependencies           โ”‚
โ”‚  7. Authentication Failures         - Weak login mechanisms           โ”‚
โ”‚  8. Data Integrity Failures        - Software/datatampering          โ”‚
โ”‚  9. Security Logging Failures      - Missing audit trails            โ”‚
โ”‚  10. Server-Side Request Forgery   - SSRF attacks                    โ”‚
โ”‚                                                                       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

SQL Injection

SQL injection occurs when user input is included in database queries without proper sanitization, allowing attackers to manipulate the query.

Vulnerable Code

# โŒ NEVER DO THIS - SQL Injection Vulnerability
user_input = request.form['username']
query = f"SELECT * FROM users WHERE username = '{user_input}'"

# Attacker input: ' OR '1'='1
# Results in: SELECT * FROM users WHERE username = '' OR '1'='1'
# This returns ALL users!

Prevention with Parameterized Queries

# โœ… Using parameterized queries - SAFE
from sqlalchemy import text

# Option 1: SQLAlchemy ORM
user = db.query(User).filter(User.username == username).first()

# Option 2: Parameterized raw query
query = text("SELECT * FROM users WHERE username = :username")
result = db.execute(query, {"username": username})

# Option 3: psycopg2 with parameterized queries
cursor.execute(
    "SELECT * FROM users WHERE username = %s",
    (username,)
)

# Option 4: Django ORM (safe by default)
user = User.objects.get(username=username)

ORM is Not Immune

# Even ORMs can be vulnerable with raw queries
# โŒ Dangerous - don't do this
User.raw(f"SELECT * FROM users WHERE username = '{username}'")

# โœ… Safe - use parameter binding
User.raw("SELECT * FROM users WHERE username = %s", [username])

Cross-Site Scripting (XSS)

XSS attacks inject malicious scripts into web pages viewed by other users.

Types of XSS

Type Description Example
Reflected Attack in URL parameter ?name=<script>alert(1)</script>
Stored Malicious script saved to DB Comment with <script> tag
DOM-based Client-side manipulation innerHTML with user input

Vulnerable Code

# โŒ Reflected XSS - vulnerable
@app.route('/search')
def search():
    query = request.args.get('q', '')
    return f'<h1>Results for: {query}</h1>'

# If attacker visits: /search?q=<script>alert('XSS')</script>
# The script will execute in victim's browser

Prevention

# โœ… Use template engines with auto-escaping (Jinja2 is safe by default)
from flask import Flask, render_template_string

# Safe template - Jinja2 auto-escapes
template = '''
<h1>Results for: {{ query }}</h1>
'''
# {{ query }} is automatically escaped!

# โœ… Use template inheritance
@app.route('/search')
def search():
    query = request.args.get('q', '')
    return render_template('search.html', query=query)

# search.html
# {% extends "base.html" %}
# {% block content %}
# <h1>Results for: {{ query }}</h1>  {# Auto-escaped #}
# {% endblock %}

# โœ… Content Security Policy
@app.after_request
def add_csp(response):
    response.headers['Content-Security-Policy'] = (
        "default-src 'self'; "
        "script-src 'self' 'unsafe-inline' https://trusted.cdn.com; "
        "style-src 'self' 'unsafe-inline';"
    )
    return response

JavaScript XSS Prevention

// โŒ Dangerous - don't use innerHTML with user input
element.innerHTML = userInput;

// โœ… Safe alternatives
// 1. Use textContent
element.textContent = userInput;

// 2. Use innerText
element.innerText = userInput;

// 3. Use createTextNode
const textNode = document.createTextNode(userInput);
element.appendChild(textNode);

// 4. If HTML needed, use DOMPurify
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput);

Cross-Site Request Forgery (CSRF)

CSRF tricks users into performing unwanted actions on authenticated sites.

The Attack

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    CSRF Attack Flow                                     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                       โ”‚
โ”‚  Attacker                    Victim's Browser           Target Site   โ”‚
โ”‚     |                             |                        |            โ”‚
โ”‚     |  sends malicious page       |                        |            โ”‚
โ”‚     |---------------------------->|                        |            โ”‚
โ”‚     |                             |                        |            โ”‚
โ”‚     |  <form action="bank.com    |                        |            โ”‚
โ”‚     |        /transfer">          |                        |            โ”‚
โ”‚     |                             |                        |            โ”‚
โ”‚     |                             | POST /transfer?to=att  |            โ”‚
โ”‚     |                             |----------------------->|           โ”‚
โ”‚     |                             | Cookie: session=abc123 |            โ”‚
โ”‚     |                             |                        |            โ”‚
โ”‚     |                             |     Transfer done!    |            โ”‚
โ”‚     |                             |<-----------------------|           โ”‚
โ”‚                                                                       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Prevention

# Flask-WTF for CSRF protection
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired

class TransferForm(FlaskForm):
    recipient = StringField('Recipient', validators=[DataRequired()])
    amount = StringField('Amount', validators=[DataRequired()])
    submit = SubmitField('Transfer')

# Templates automatically include CSRF token
# <form method="post">
#     {{ form.hidden_tag() }}  {# CSRF token #}
#     ...
# </form>

# โœ… Enable CSRF protection globally
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)

# โœ… Custom CSRF token handling
@app.route('/api/transfer', methods=['POST'])
def api_transfer():
    csrf_token = request.headers.get('X-CSRF-Token')
    if not validate_csrf_token(csrf_token):
        return {'error': 'Invalid CSRF token'}, 403
    
    # Process transfer
    return {'success': True}

SameSite Cookies

# โœ… Set SameSite cookie attribute
@app.after_request
def set_samesite_cookie(response):
    response.set_cookie(
        'session',
        session_id,
        samesite='Strict',  # or 'Lax'
        secure=True,
        httponly=True
    )
    return response

Broken Access Control

Access control flaws allow users to access resources they shouldn’t.

Vulnerable Code

# โŒ Missing authorization check
@app.route('/user/<id>')
def get_user(id):
    # Any authenticated user can view ANY user!
    return User.query.get(id)

# โœ… Proper authorization
@app.route('/user/<id>')
@login_required
def get_user(id):
    # Check if current user can access this user
    if current_user.id != int(id) and not current_user.is_admin:
        abort(403)
    
    return User.query.get(id)

Implementation

from functools import wraps

def require_permission(permission):
    """Decorator to check permissions."""
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            if not current_user.has_permission(permission):
                abort(403)
            return f(*args, **kwargs)
        return decorated_function
    return decorator


# Usage
@app.route('/admin/users')
@login_required
@require_permission('users:read')
def admin_users():
    return User.query.all()


@app.route('/admin/users', methods=['POST'])
@login_required
@require_permission('users:write')
def create_user():
    # Create user
    return {'id': new_user.id}


class Permission:
    """Permission constants."""
    USERS_READ = 'users:read'
    USERS_WRITE = 'users:write'
    USERS_DELETE = 'users:delete'
    ADMIN = 'admin'

Command Injection

Attackers execute system commands through vulnerable applications.

Vulnerable Code

# โŒ NEVER DO THIS
@app.route('/ping')
def ping():
    host = request.args.get('host')
    # Attacker input: 8.8.8.8; rm -rf /
    result = os.system(f"ping -c 1 {host}")
    return f'Result: {result}'

# โœ… Use subprocess with list
@app.route('/ping')
def ping():
    host = request.args.get('host')
    # Validates host is just a hostname/IP
    import shlex
    if not shlex.quote(host) == host:
        abort(400)
    
    result = subprocess.run(
        ['ping', '-c', '1', host],
        capture_output=True,
        text=True
    )
    return result.stdout

Secure Headers

# โœ… Security headers
@app.after_request
def add_security_headers(response):
    # Prevent clickjacking
    response.headers['X-Frame-Options'] = 'DENY'
    
    # XSS protection
    response.headers['X-XSS-Protection'] = '1; mode=block'
    
    # Prevent MIME sniffing
    response.headers['X-Content-Type-Options'] = 'nosniff'
    
    # HSTS - force HTTPS
    response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
    
    # Content Security Policy
    response.headers['Content-Security-Policy'] = (
        "default-src 'self'; "
        "script-src 'self' 'unsafe-inline' https://cdn.example.com; "
        "style-src 'self' 'unsafe-inline'; "
        "img-src 'self' data: https:; "
        "connect-src 'self' https://api.example.com"
    )
    
    # Referrer Policy
    response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
    
    # Permissions Policy
    response.headers['Permissions-Policy'] = (
        'geolocation=(), '
        'camera=(), '
        'microphone=()'
    )
    
    return response

Password Security

import bcrypt
import secrets


def hash_password(password: str) -> str:
    """Hash password using bcrypt."""
    salt = bcrypt.gensalt(rounds=12)
    return bcrypt.hashpw(password.encode(), salt).decode()


def verify_password(password: str, hashed: str) -> bool:
    """Verify password against hash."""
    return bcrypt.checkpw(password.encode(), hashed.encode())


def generate_secure_token(length: int = 32) -> str:
    """Generate cryptographically secure token."""
    return secrets.token_urlsafe(length)


# โœ… Using argon2-cffi (better than bcrypt for new projects)
from argon2 import PasswordHasher

ph = PasswordHasher(
    time_cost=2,
    memory_cost=102400,
    parallelism=2,
    hash_len=32,
    salt_len=16
)

def hash_password_argon2(password: str) -> str:
    return ph.hash(password)

def verify_password_argon2(password: str, hash: str) -> bool:
    try:
        return ph.verify(hash, password)
    except:
        return False

Input Validation

from pydantic import BaseModel, validator, Field
from typing import Optional


class UserRegistration(BaseModel):
    username: str = Field(..., min_length=3, max_length=30)
    email: str
    password: str = Field(..., min_length=8)
    age: Optional[int] = Field(None, ge=0, le=150)
    
    @validator('username')
    def username_alphanumeric(cls, v):
        if not v.isalnum():
            raise ValueError('Username must be alphanumeric')
        return v
    
    @validator('email')
    def email_valid(cls, v):
        import re
        if not re.match(r'^[\w\.-]+@[\w\.-]+\.\w+$', v):
            raise ValueError('Invalid email format')
        return v.lower()
    
    @validator('password')
    def password_strong(cls, v):
        import re
        if not re.search(r'[A-Z]', v):
            raise ValueError('Password must contain uppercase')
        if not re.search(r'[a-z]', v):
            raise ValueError('Password must contain lowercase')
        if not re.search(r'\d', v):
            raise ValueError('Password must contain digit')
        return v


# Usage
try:
    user = UserRegistration(
        username='john123',
        email='[email protected]',
        password='SecurePass123',
        age=25
    )
except ValidationError as e:
    print(e.errors())

Security Checklist

Category Checklist Item
Authentication Use strong password hashing (bcrypt/argon2)
Authentication Implement MFA where appropriate
Authorization Check permissions on every request
Input Validate and sanitize all input
Output Escape output based on context
Database Use parameterized queries
Sessions Use secure, HttpOnly cookies
API Rate limit endpoints
API Require authentication
Headers Set security headers
Dependencies Keep libraries updated
Secrets Never commit to version control

Conclusion

Web security requires a defense-in-depth approach. No single technique provides complete protection, but implementing the patterns in this guide significantly reduces your attack surface.

Key takeaways:

  1. Validate input - Never trust user data
  2. Parameterize queries - Prevent SQL injection
  3. Escape output - Prevent XSS
  4. Use CSRF tokens - Protect state-changing operations
  5. Check permissions - Implement proper authorization
  6. Set security headers - Add defense layers
  7. Keep updated - Patch vulnerabilities promptly

By building security into your applications from the start and following these best practices, you’ll create more secure web applications that protect both your users and your organization.

Resources

Comments