Skip to content

Flask debug server bound to 0.0.0.0 enables unauthenticated Werkzeug RCE on any reachable host #24

@tg12

Description

@tg12

Summary

Both app.py and app-exe.py start the Flask development server with debug=True bound to all network interfaces (host="0.0.0.0"). When debug mode is enabled, Werkzeug activates its interactive debugger on every unhandled exception. Any caller who can reach the port and trigger an exception gains access to a browser-based Python REPL with full server-process privileges.

Evidence

  • app.py:3164: app.run(host="0.0.0.0", port=8080, debug=True)
  • app-exe.py:1907: app.run(host="0.0.0.0", port=8000, debug=True)

Both are the canonical entrypoints committed to the repository and documented in the README.

Why this matters

The Werkzeug debugger PIN provides a weak gate at best. Documented PIN calculation bypass techniques exist for instances where an attacker can read /proc/self/cgroup or similar file paths. On a reachable host, this is unauthenticated remote code execution on the server process. The application already contains routes that are expected to throw exceptions under normal operation (fabricated-data fallbacks, missing API keys, network errors), so exception triggering is not hypothetical.

Attack or failure scenario

An attacker reaches port 8080 from the local network or a reachable deployment. They trigger an unhandled exception via any route that lacks a try/except (or via a malformed request). The Werkzeug traceback page appears with an interactive console. Using publicly documented PIN bypass techniques (reading /proc/self/cgroup, /etc/machine-id, and network interface data), the attacker computes or bypasses the PIN and executes arbitrary Python code under the server's process identity.

Root cause

The development server entrypoint was committed without a production deployment path. There is no WSGI wrapper (gunicorn, uWSGI) or debug=False override, and the README does not distinguish between development and production startup.

Recommended fix

  1. Remove debug=True from all committed entrypoints immediately.
  2. Bind to 127.0.0.1 for local development; never bind to 0.0.0.0 in a committed entrypoint.
  3. Replace app.run(...) entrypoints with a WSGI command (gunicorn app:app) as the documented production start method.
  4. Add a CI check that fails if debug=True appears in any committed Python entrypoint.

Acceptance criteria

  • No committed entrypoint passes debug=True to app.run(...).
  • The documented startup command uses a production WSGI server.
  • 0.0.0.0 is not the default host binding in any committed entrypoint.
  • CI fails if debug=True is reintroduced.

Suggested labels

security, production-readiness, bug

Priority

P0

Severity

Critical — the Werkzeug interactive debugger on 0.0.0.0 is a well-documented unauthenticated RCE surface on any reachable host.

Confidence

Confirmed — both committed entrypoints contain the exact flag.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions