OWASP Top 10:2025 Course — Module 2 of 10. Course home · Previous: Module 1 — Broken Access Control · Next: Module 3 — Software Supply Chain Failures

Learn first: what is Security Misconfiguration

Security Misconfiguration (A02:2025) is what happens when every individual setting looks small — directory listing on, a default admin password, debug mode left on in production, a missing security header — but together they hand an attacker a map of your infrastructure and a front door. None of these require a clever exploit; they require only that someone forgot to turn something off before shipping.

Browser ──▶ Nginx ──▶ Flask app
             │            │
             │            ├─ /admin          no auth check
             │            ├─ /admin/config    dumps secret_key, DB, AWS bucket
             │            └─ /error-demo      stack trace on any error
             │
             ├─ /files/   directory listing ON (autoindex)
             └─ no security headers at all (CSP, HSTS, X-Frame-Options...)

Four misconfigurations, one request each, zero exploit development.

Scenario briefing

You’re doing a quick external review of AcmeCorp’s internal portal before it goes live on a public subdomain. You have no credentials and no special access — just the URL.

Lab setup

  1. Download: Download the lab (zip)
  2. Prerequisites: Docker Engine 20+, Docker Compose v2.
  3. Start it:
    cd owasp-a02-security-misconfiguration-lab
    docker compose up -d --build
  4. Check it worked: curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8080/ should print 200.

Missions

Mission 1 — open bucket. List the files directory and read the quarterly revenue report.

Show solution

Solution — Mission 1

curl -s http://127.0.0.1:8080/files/
curl -s http://127.0.0.1:8080/files/q3-revenue.csv | tail -1

nginx.conf has autoindex on; for /files/ — Nginx lists the directory, no application involved at all. The last line of the CSV holds FLAG{a02-open-bucket-directory-listing}.

Mission 2 — unauthenticated config leak. Find the endpoint that leaks the app’s internal configuration without logging in.

Show solution

Solution — Mission 2

curl -s http://127.0.0.1:8080/admin/config

Returns the Flask secret_key, the admin credentials, the database URL, and an internal S3 bucket name — plus "ops_flag": "FLAG{a02-config-leak-no-auth}" — all with zero authentication, because the route was never protected and nothing in front of it blocks it either.

Mission 3 — default credentials. Log in with the vendor’s default admin password, then call /profile.

Show solution

Solution — Mission 3

curl -s -c cj.txt -d "username=admin&password=admin" http://127.0.0.1:8080/login
curl -s -b cj.txt http://127.0.0.1:8080/profile

admin / admin was never changed from the seeded default. /profile returns FLAG{a02-default-creds-admin-admin} for the admin role.

Mission 4 — verbose errors. Trigger an error and see what the response leaks.

Show solution

Solution — Mission 4

curl -s "http://127.0.0.1:8080/error-demo?input=invalid"

APP_DEBUG=true means Flask’s debug error page renders — a full Python traceback with absolute file paths, line numbers, and the exact eval() call that failed. That’s a roadmap for finding the next bug.

Mission 5 — harden it and verify. Apply the hardened Nginx config and app settings (docker-compose.hardened.yml has the reference), then check.

Show solution

Solution — Mission 5

docker compose down
docker compose -f docker-compose.hardened.yml up -d --build
bash scripts/check-misconfigs.sh
python3 lab.py verify

The hardened stack sets server_tokens off, adds six security headers, returns 404 for /admin and /admin/config at the Nginx layer, disables debug mode (generic error page), and changes the admin password away from the default. All checks go green.

Hints

Fix it + re-test

The fixes are entirely configuration, not code: security headers at the reverse-proxy layer, return 404 for internal routes, server_tokens off, APP_DEBUG=false, and a real password instead of the vendor default. Re-run bash scripts/check-misconfigs.sh — every line should flip to green.

Detect it

Bake check-misconfigs.sh-style checks into CI so a regression (someone flips autoindex on again, or debug mode creeps back in staging configs copied to prod) fails the build instead of shipping. In production, alert on any 200 response from /admin* paths from an IP that never authenticated.

Quick self-check

  1. Why is "it’s just a config file" not a good reason to skip a security review of infrastructure changes?
  2. What’s the difference between this module’s /admin leak and Module 1’s /admin leak?
  3. Why does disabling debug mode matter even if the app has no other known bugs?
Show answers

Answers

  1. Config changes ship to production just as fast as code, and a single wrong line (autoindex on, a default password left in an env var) is directly exploitable with no code execution needed.
  2. Module 1’s /admin was an application-level missing authorization check (broken access control); this module’s exposure stack is layered — missing headers, directory listing, and debug mode are infrastructure/configuration issues, not a single code bug.
  3. A debug error page leaks file paths, framework versions, and sometimes variable values — reconnaissance that shortens the time to find the next real bug, even on an otherwise solid app.