""" vulnerable-webapp.py.txt ======================== INTENTIONALLY VULNERABLE Flask web application for CTF / code-auditing practice. DO NOT deploy or run in production. All vulnerabilities are educational. """ import os import pickle import base64 import sqlite3 from flask import Flask, request, render_template_string, send_file, redirect, session app = Flask(__name__) app.secret_key = "supersecret123" # TODO: fix this later # ---------- Database Setup ---------- DATABASE = "app.db" def get_db(): conn = sqlite3.connect(DATABASE) conn.row_factory = sqlite3.Row return conn def init_db(): db = get_db() db.execute(""" CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE, password TEXT, role TEXT DEFAULT 'user' ) """) db.execute(""" CREATE TABLE IF NOT EXISTS posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, author TEXT, title TEXT, body TEXT ) """) # Hardcoded admin credentials -- TODO: fix this later db.execute( "INSERT OR IGNORE INTO users (username, password, role) VALUES (?, ?, ?)", ("admin", "admin123", "admin") ) db.commit() init_db() # ---------- Hardcoded Credentials ---------- API_KEY = "sk-live-4f3c2b1a0d9e8f7c6b5a4d3e2f1a0b9c" # TODO: move to env ADMIN_PASSWORD = "Passw0rd!" # TODO: fix this later DB_CONNECTION_STRING = "postgresql://appuser:s3cretDBpass@10.0.0.5:5432/production" # ---------- Routes ---------- # VULNERABILITY: SQL Injection (string concatenation) @app.route("/login", methods=["GET", "POST"]) def login(): if request.method == "POST": username = request.form.get("username", "") password = request.form.get("password", "") db = get_db() # Vulnerable: string formatting in SQL query query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'" user = db.execute(query).fetchone() # TODO: fix this later if user: session["user"] = username session["role"] = user["role"] return redirect("/dashboard") return "Invalid credentials", 401 return '''
''' # VULNERABILITY: XSS (unescaped user input in template) @app.route("/search") def search(): query = request.args.get("q", "") db = get_db() results = db.execute( "SELECT * FROM posts WHERE title LIKE ?", (f"%{query}%",) ).fetchall() # Vulnerable: user input rendered directly into HTML without escaping html = f"""

Search Results for: {query}

""" return render_template_string(html) # TODO: fix this later # VULNERABILITY: Command Injection (os.system with user input) @app.route("/ping") def ping(): host = request.args.get("host", "127.0.0.1") # Vulnerable: user input passed directly to shell os.system(f"ping -c 3 {host}") # TODO: fix this later return f"Pinged {host}" # VULNERABILITY: Path Traversal (no path sanitization) @app.route("/download") def download(): filename = request.args.get("file", "") # Vulnerable: no sanitization of filename, allows ../../../etc/passwd filepath = os.path.join("/var/www/uploads", filename) return send_file(filepath) # VULNERABILITY: Insecure Deserialization (pickle.loads on user data) @app.route("/load-session", methods=["POST"]) def load_session(): data = request.form.get("session_data", "") try: decoded = base64.b64decode(data) # Vulnerable: deserializing untrusted user input with pickle session_obj = pickle.loads(decoded) # TODO: fix this later return f"Session loaded: {session_obj}" except Exception as e: return f"Error: {e}", 400 # VULNERABILITY: Broken Access Control (no auth check) @app.route("/admin/users") def admin_users(): # No authentication or authorization check db = get_db() users = db.execute("SELECT id, username, role FROM users").fetchall() return "
".join(f"{u['id']}: {u['username']} ({u['role']})" for u in users) # VULNERABILITY: Information Disclosure (verbose error + debug mode) @app.route("/debug") def debug_info(): return { "secret_key": app.secret_key, "api_key": API_KEY, "db_string": DB_CONNECTION_STRING, "environment": dict(os.environ), } # VULNERABILITY: Open Redirect @app.route("/redirect") def open_redirect(): url = request.args.get("url", "/") # Vulnerable: no validation of redirect target return redirect(url) if __name__ == "__main__": app.run(debug=True, host="0.0.0.0", port=5000) # debug=True in production!