Flask gives you nothing out of the box. No ORM. No authentication middleware. No CSRF protection for APIs. Developers love it because it is fast to write. Attackers love it because developers inevitably make mistakes when building their own security primitives. We target those custom implementations directly.

Auditing session key storage and signing

Flask uses client-side signed session cookies by default. The session data is serialized, usually compressed, and signed using the application's SECRET_KEY.

If your SECRET_KEY is guessable, weak, or exposed in a public repository, attackers can decode the cookie, modify session values, sign it with the key, and send it back. Flask’s default session cookie is signed, not encrypted. This means anyone can read the contents of the cookie by simply decoding the base64 payload. If you store sensitive data like password hashes or PII in the Flask session dictionary, you leak it to the client immediately.

During a security audit, we check if the key is loaded from secure environment variables. We also use brute-force utilities like flask-unsign to test the strength of the session key. We run dictionary attacks against the signature. If we recover the key, we forge a cookie containing {'user_id': 1, 'role': 'admin'} and bypass all authentication mechanisms.

Failure example

A development secret signs production sessions

We frequently find Flask applications in production using default tutorial keys like `super_secret_key` or `changeme123`. A short or reused SECRET_KEY lets an attacker forge Flask session cookies after recovering the key in seconds. Generate a long random production secret using `os.urandom(24)`, keep it outside source control, rotate it after exposure, and invalidate existing sessions.

Raw SQL Injection in Flask endpoints

While Object-Relational Mappers (ORMs) like SQLAlchemy prevent database injections when used correctly, developers often write raw SQL strings to process custom requests or optimize complex joins. If user input is concatenated directly into these strings, SQL injection occurs instantly.

# VULNERABLE: Direct string concatenation
query = f"SELECT * FROM transactions WHERE user_id = '{user_input}'"
db.engine.execute(query)

# SECURE: Using parameterized queries
query = "SELECT * FROM transactions WHERE user_id = :user_id"
db.session.execute(text(query), {'user_id': user_input})

We automate SQL injection discovery using SQLMap, but we confirm and exploit it manually. We extract the database schema. We dump the user tables. We check if the database user has unnecessary privileges, like the ability to read local files via `LOAD DATA INFILE` or execute system commands via `xp_cmdshell`. A single injection flaw in a Flask API often leads to complete infrastructure compromise.

Testing route-level authorization and CORS

Flask does not enforce access controls on routes unless decorators like @login_required or custom Role-Based Access Control (RBAC) wrappers are explicitly applied. We check all routes to identify endpoints that lack session verification. Developers often secure the GET method but forget to secure the POST method on the same route.

We also check Flask-CORS configurations. If the backend accepts requests from any origin (*) and allows credentials (like cookies or Authorization headers), malicious websites can make calls on behalf of authenticated users. This enables Cross-Origin Resource Sharing (CORS) exploitation, which functions similarly to CSRF but allows the attacker to read the response.

We test Insecure Direct Object Reference (IDOR) extensively. If an endpoint expects a transaction ID like `/api/transactions/1050`, we change it to `/api/transactions/1051`. If the Flask view function fetches the transaction without verifying that the `user_id` on the transaction matches the `current_user.id` from the session, we steal the data.

Jinja2 Server-Side Template Injection (SSTI)

If your Flask application renders HTML templates using Jinja2 and incorporates raw user input into the template string rather than passing it as a context variable, you are vulnerable to SSTI. Attackers inject Jinja syntax `{{ 7 * 7 }}` to evaluate expressions. We escalate this to Remote Code Execution (RCE) by navigating the Python Method Resolution Order (MRO) to access the `os.popen` class and execute system commands directly on the host server.

Flask API Security Checklist

If you run a Flask API in production, apply these controls immediately:

  1. Set a strong SECRET_KEY: Generate a cryptographically secure key and load it from environment variables. Never hardcode it.
  2. Use parameter bindings: Never concatenate string inputs inside database queries. Use ORM operations or bound variables strictly.
  3. Apply explicit decorators: Use authentication middleware decorators on all routes that should not be public. Apply them to every HTTP method allowed on the route.
  4. Enforce strict CORS policies: Define exact origins in your `flask_cors` setup. Never use `origins="*"` alongside `supports_credentials=True`.
  5. Disable Werkzeug debugger: Ensure `app.run(debug=True)` is never deployed to production. The Werkzeug interactive debugger allows unauthenticated remote code execution by design.

Test the deployed Flask app

  1. Map routes: Export app.url_map in a safe test environment and compare it with proxy traffic. Include blueprint prefixes, alternate methods, and routes registered by extensions. Ensure no forgotten debug endpoints remain active.
  2. Test sessions: Inspect Secure, HttpOnly, SameSite, expiry, logout, password change, and secret rotation. Verify cookies cannot be intercepted via JavaScript or over unencrypted connections.
  3. Test authorization: Replay every write with no session, a lower role, and a user from another tenant. Decorators must cover the route and the service method that changes data.
  4. Test parsing: Send JSON, form, multipart, duplicate keys, wrong content types, and large bodies. Confirm validation runs before database work. Deny-list massive payloads that cause JSON parsing Denial of Service (DoS).
  5. Test deployment flags: Check DEBUG, proxy header trust, host validation, CORS, error pages, and the production WSGI server. Do not run Flask's built-in server in production; use Gunicorn or uWSGI behind Nginx.

Keep the route map, sanitized configuration, raw requests, application logs, and database before-and-after values. The final evidence must show that rejected requests create no side effect.

Let Simpa Labs audit your Flask API

We perform manual penetration testing of Python backends and microservices. We review your routing configurations, check your database interfaces, test your authentication endpoints, and find the logical bypasses that scanners miss.

Book a Flask Pentest