Security Audit Report
Vulnerable Flask Application
Language: Python | Framework: Flask
Tools: Bandit, Manual Inspection
Standard: OWASP Top 10 (2021)
Section 1 — Vulnerable Flask App (Code Review)
The following Python Flask application was selected as the audit target. It intentionally contains nine security vulnerabilities across six OWASP Top 10 categories. Each vulnerability is annotated in the source code with a warning comment.
1.1 Application Overview
Framework: Python / Flask | Database: SQLite (in-memory) | Auth: Session-based
The app exposes the following routes:
- /login — Authentication with SQL Injection vulnerability
- /greet — Greeting page with XSS vulnerability
- /search — User search with SQL Injection vulnerability
- /load_profile — Profile loader with Insecure Deserialization
- /read_file — File reader with Path Traversal vulnerability
- /admin — Admin panel with Broken Access Control
- /dashboard — User dashboard
1.2 Vulnerability 1 — SQL Injection (Login)
Severity: HIGH
Location: /login route, line ~52
OWASP: A03:2021 — Injection
Description: User-supplied username and password are directly interpolated into an SQL query string using an f-string. An attacker can manipulate the query logic to bypass authentication entirely.
Vulnerable Code
query = f"SELECT * FROM users WHERE username = '{username}'
AND password = '{password}'"
c.execute(query)Proof of Concept
Enter the following in the username field, with any password:
' OR '1'='1' -1.3 Vulnerability 2 — SQL Injection (Search)
Severity: HIGH
Location: /search route, line ~85
OWASP: A03:2021 — Injection
Description: The search query parameter is interpolated directly into a LIKE clause, allowing an attacker to dump all records or manipulate query logic.
c.execute(f"SELECT username, role FROM users
WHERE username LIKE '%{query_param}%'")1.4 Vulnerability 3 — Hardcoded Secret Key
Severity: HIGH
Location: Line ~30 (app config)
OWASP: A02:2021 — Cryptographic Failures
Description: The Flask secret key is hardcoded as a string literal in source code. Anyone with access to the code can forge session cookies and impersonate any user.
app.secret_key = "supersecretkey123"1.5 Vulnerability 4 — Hardcoded Credentials
Severity: HIGH
Location: init_db() function, line ~38
OWASP: A07:2021 — Identification and Authentication Failures
Description: The admin password is hardcoded in plaintext inside the source code and stored without any hashing. This exposes credentials to anyone who can read the source or database.
c.execute("INSERT INTO users VALUES
(1, 'admin', 'admin123', 'admin')")1.6 Vulnerability 5 — Cross-Site Scripting (XSS)
Severity: MEDIUM
Location: /greet route
OWASP: A03:2021 — Injection
Description: The Jinja2 |safe filter is applied to a user-supplied URL parameter, disabling auto-escaping. An attacker can inject a <script> tag that executes in the victim's browser.
return render_template_string(
"<h2>Hello, {{ name|safe }}!</h2>", name=name
)Proof of concept URL:
/greet?name=<script>alert('XSS')</script>1.7 Vulnerability 6 — Insecure Deserialization
Severity: HIGH
Location: /load_profile route
OWASP: A08:2021 — Software and Data Integrity Failures
Description: pickle.loads() is called on raw user-supplied request body bytes. A malicious pickle payload can execute arbitrary operating system commands on the server.
data = request.data # raw bytes from user
profile = pickle.loads(data) # DANGEROUS1.8 Vulnerability 7 — Path Traversal
Severity: HIGH
Location: /read_file route
OWASP: A01:2021 — Broken Access Control
Description: The filename parameter is passed directly to os.path.join without validation. An attacker can use ../ sequences to traverse outside the intended directory and read arbitrary files.
filename = request.args.get('filename', '')
filepath = os.path.join(base_dir, filename) # no sanitization
# Attack: /read_file?filename=../../etc/passwd1.9 Vulnerability 8 — Broken Access Control (Admin)
Severity: MEDIUM
Location: /admin route
OWASP: A01:2021 — Broken Access Control
Description: The admin route only checks whether a session key exists, not whether the user's role is 'admin'. Any authenticated user can access the admin panel.
if "user" in session: # only checks login, not role
return admin_panel()1.10 Vulnerability 9 — Debug Mode Enabled
Severity: MEDIUM
Location: app.run(debug=True)
OWASP: A05:2021 — Security Misconfiguration
Description: Running Flask with debug=True exposes the Werkzeug interactive debugger in the browser. If triggered in production, it allows an attacker to execute arbitrary Python code remotely.
app.run(debug=True) # NEVER use in productionSection 2 — Recommendations & Best Practices
The following table maps each vulnerability to its recommended fix and the relevant OWASP reference.

2.1 General Secure Coding Principles
- Never trust user input — validate and sanitize everything from the browser or API
- Apply the principle of least privilege — users should only access what they need
- Keep dependencies up to date — run 'pip install safety && safety check' regularly
- Store secrets in environment variables, never in source code
- Use HTTPS in production to protect data in transit
- Use a Web Application Firewall (WAF) as a secondary defence layer
- Follow the OWASP Top 10 as your baseline security checklist
Section 3 — Findings & Remediation Steps
3.1 Summary of Findings

3.2 Fix 1 — SQL Injection
Before (Vulnerable)
query = f"SELECT * FROM users WHERE username = '{username}'
AND password = '{password}'"
c.execute(query)After (Fixed)
query = "SELECT * FROM users WHERE username = ? AND password = ?"
c.execute(query, (username, password)) # parameterized query3.3 Fix 2 — Hardcoded Secret Key
Before (Vulnerable)
app.secret_key = "supersecretkey123"After (Fixed)
import os
from dotenv import load_dotenv
load_dotenv()
app.secret_key = os.environ.get("SECRET_KEY")3.4 Fix 3 — Hardcoded Credentials
Before (Vulnerable)
c.execute("INSERT INTO users VALUES (1, 'admin', 'admin123', 'admin')")After (Fixed)
from werkzeug.security import generate_password_hash
hashed = generate_password_hash(os.environ.get("ADMIN_PASSWORD"))
c.execute("INSERT INTO users VALUES (1, 'admin', ?, 'admin')", (hashed,))3.5 Fix 4 — Cross-Site Scripting (XSS)
Before (Vulnerable)
return render_template_string("<h2>Hello, {{ name|safe }}!</h2>", name=name)After (Fixed)
#Remove |safe - Jinja2 auto-escapes by default
return render_template_string("<h2>Hello, {{ name }}!</h2>", name=name)3.6 Fix 5 — Insecure Deserialization
Before (Vulnerable)
profile = pickle.loads(request.data)After (Fixed)
import json
profile = json.loads(request.data) # safe alternative to pickle3.7 Fix 6 — Path Traversal
Before (Vulnerable)
filepath = os.path.join(base_dir, filename) # no validationAfter (Fixed)
safe_path = os.path.realpath(os.path.join(base_dir, filename))
if not safe_path.startswith(os.path.realpath(base_dir)):
return "Access denied", 403
filepath = safe_path3.8 Fix 7 — Broken Access Control
Before (Vulnerable)
if "user" in session:After (Fixed)
if session.get("role") != "admin":
return "Forbidden", 4033.9 Fix 8 — Debug Mode
Before (Vulnerable)
app.run(debug=True)After (Fixed)
app.run(debug=os.environ.get("FLASK_DEBUG", "false") == "true")Conclusion
The reviewed Flask application contained nine security vulnerabilities across six OWASP Top 10 categories. Five findings were rated High severity, including SQL Injection, Insecure Deserialization, and Path Traversal — all of which could lead to full system compromise if exploited in a production environment.
Remediation steps have been provided for every finding. Priority should be given to the SQL Injection, Insecure Deserialization, and Hardcoded Secrets vulnerabilities due to their critical potential impact.
Running Bandit static analysis is recommended as an ongoing practice:
pip install bandit
bandit -r vulnerable_app.py -f txt -o bandit_report.txt