1. Werkzeug debugger: Remote Code Execution on live servers
Flask uses the Werkzeug WSGI toolkit as its underlying server. During development, Werkzeug provides an interactive debugging console at any error page URL. This console allows execution of arbitrary Python code in the server process. If debug=True is set in production, anyone who can trigger an unhandled exception in your application gets an interactive Python shell running with your server's privileges.
The debugger console requires a PIN to access, but the PIN is deterministic and can be calculated from information that is sometimes available through information disclosure vulnerabilities elsewhere in the application. We test both the debugger activation and PIN bypass as part of every Flask assessment.
2. Jinja2 Server-Side Template Injection (SSTI)
SSTI in Flask occurs when user input reaches a render_template_string() call or a manually constructed template string. The payload {{ 7*7 }} returns 49 in the response if SSTI is present. From there, an attacker uses Python's object traversal to reach os.system() and execute shell commands on the server:
# VULNERABLE pattern: user input inside render_template_string
@app.route('/greet')
def greet():
name = request.args.get('name', '')
# Attacker sends: {{ config.items() }} or {{ ''.__class__.__mro__[1].__subclasses__() }}
return render_template_string(f'<h1>Hello {{ name }}</h1>') The secure pattern is to always use render_template() with a template file on disk, passing user data as a named variable, never by constructing template strings with Python string formatting.
3. Client-side session cookie analysis
Flask signs session cookies but does not encrypt them. The session payload is a base64-encoded JSON object. We decode the cookie payload to inspect what data is stored and whether any server-trust decisions are made based on that data. Common findings include role strings ("role": "user"), KYC flags, and internal user IDs stored inside the client cookie that can be tampered with if the SECRET_KEY is weak or leaked.
# Decode Flask session cookie payload (the part before the first dot)
echo "eyJ1c2VyX2lkIjoxLCJyb2xlIjoidXNlciJ9" | base64 -d
# Output: {"user_id":1,"role":"user"} 4. Blueprint and route access control gaps
Flask uses Blueprints to organize routes into modules. A common architectural mistake is applying before_request authentication hooks only to the top-level app, not to registered Blueprints. Routes registered on a Blueprint that does not have its own before_request filter (and does not rely on the app-level filter) receive unauthenticated requests without triggering any login check. We map every registered Blueprint and test authentication enforcement on each one independently.
Werkzeug debugger active on staging environment with public DNS
During a security assessment of a Nigerian logistics platform, we found the Flask application running with debug=True on a staging environment that had a public-facing domain name. By requesting a non-existent route to trigger a 404 exception, we were presented with the Werkzeug interactive debugger. We calculated the debugger PIN from information leaks in the application's configuration endpoint and gained an interactive Python shell on the server. Fix priority: critical. Remediated by setting debug=False and using an environment variable to control the flag.
Running a Flask API or microservice in production? Schedule a security assessment.
Book a Flask PentestFrequently asked questions
What is Jinja2 SSTI and how dangerous is it in Flask?
Server-Side Template Injection (SSTI) occurs when user-controlled input is passed into a Jinja2 template rendering function without sanitization. In Flask applications, this grants the attacker Remote Code Execution on the server through Jinja2's Python expression evaluator.
How do you test Flask-Login security?
We verify that the @login_required decorator is applied to every route that requires authentication, that the user_loader callback validates session integrity, and that logout properly invalidates both the server-side session and the client-side cookie.
Is Flask's client-side session cookie secure?
Flask signs session cookies with the SECRET_KEY but does not encrypt the payload. The session data is base64-encoded and readable by anyone who holds the cookie. If sensitive data (like user roles) is stored in the session, it should be moved server-side using Flask-Session with a backend store.
Related reading
Blog: Flask API security audit deep dive · Django penetration testing · FastAPI security audits
Services: Penetration testing · API security testing