The risk of Werkzeug debug mode
The Werkzeug debugger is a powerful development tool. In production, however, it is a direct remote code execution (RCE) vector. If app.run(debug=True) is active and an unhandled exception occurs, the browser displays the interactive console. While Werkzeug requires a PIN to unlock the console by default, researchers have documented methods to guess or compute this PIN using publicly visible server information (such as the username of the user running the process and network interface MAC addresses).
We audit the codebase and container environment variables to verify that debug configurations are disabled in production:
# SECURE initialization pattern
import os
from flask import Flask
app = Flask(__name__)
# Load debug configuration strictly from environment variables
app.config['DEBUG'] = os.environ.get('FLASK_DEBUG', 'False') == 'True' SQL Injection via raw queries
Flask does not include a database layer by default, leading developers to implement raw SQL connections using drivers like psycopg2. Concatenating variables into SQL strings creates SQL injection (SQLi) vulnerabilities:
# VULNERABLE query pattern
@app.route('/api/users')
def get_user():
username = request.args.get('username')
query = f"SELECT * FROM users WHERE username = '{username}'" # SQL Injection!
cursor.execute(query)
return cursor.fetchall() The Fix: Force the use of parameter binding or SQLAlchemy ORM classes. The database engine executes parameterized values strictly as parameters, preventing SQL token manipulation:
# SECURE query pattern
@app.route('/api/users')
def get_user():
username = request.args.get('username')
# Parameterized SQL query
cursor.execute("SELECT * FROM users WHERE username = %s", (username,))
return cursor.fetchall() Session security and Cookie Hijacking
Flask uses signed cookies to store session data. The signature is computed using app.secret_key. If this key is weak (e.g., "dev", "secret") or leaked, an attacker can modify session values (like changing their user_id or admin role) and sign the cookie themselves.
During a penetration test, we attempt to brute-force the Flask session cookie signature using tools like flask-unsign:
# Decode session cookie content
flask-unsign --decode --cookie "eyJfZnJlc2giOmZhbHNlLCJ1c2VyX2lkIjoxfQ.Zp5m_Q.XYZ..."
# Brute-force the secret key using a wordlist
flask-unsign --unsign --cookie "eyJfZnJlc2giOmZhbHNlLCJ1c2VyX2lkIjoxfQ..." --wordlist wordlist.txt The Fix: Generate a high-entropy secret key using a secure random generator, and load it from environment variables. Never hardcode the secret key in the repository.
API input validation and serialization
Flask endpoints must validate JSON payloads. Without structured validation schemas (like Marshmallow, Pydantic, or Flask-RESTful), endpoints are vulnerable to type-coercion bugs, parameter pollution, and schema injection. We review your request parsers to ensure that all data types are strictly verified before execution.
Werkzeug debugger exposed on staging environment
During an API audit for a fintech microservice, we accessed the staging URL and forced a 500 error by sending a malformed request header. The server returned the Werkzeug debug console. We bypassed the console PIN using system information gathered from an adjacent directory traversal leak and achieved shell access to the staging container. Fix priority: critical. Remediated by disabling debugging and setting FLASK_ENV=production.
Running Flask microservices or Python APIs? Schedule a security audit.
Book a Flask API AuditFrequently asked questions
Why is debug mode in Flask so dangerous in production?
Leaving `debug=True` active in production exposes Werkzeug's interactive debugger. If an error occurs, the debugger displays a stack trace containing an interactive Python shell on the web interface, allowing attackers to execute arbitrary shell commands.
How do you prevent SQL injection in Flask?
Avoid raw SQL concatenation. Utilize SQLAlchemy ORM with parameterized queries, or enforce strict input typing using frameworks like Marshmallow or Pydantic.
What is the security risk of using Flask's default cookie sessions?
Flask's default sessions are signed but not encrypted. Anyone can read the contents of the session cookie by base64-decoding it. If you store sensitive fields (like user IDs or permissions) there, attackers can analyze them and attempt privilege escalation.
Related reading
Blog: Securing Django APIs · Open banking API security · JWT token mistakes
Services: API security testing · Secure architecture review