MEDIUM open redirectflask

Open Redirect in Flask

How Open Redirect Manifests in Flask

Open redirect vulnerabilities in Flask applications commonly arise from improper handling of user-supplied redirect URLs in route parameters or query strings. Attackers exploit these to craft phishing links that appear to originate from a trusted domain but redirect users to malicious sites after authentication. In Flask, this often occurs in login, logout, or OAuth callback routes where a 'next' parameter dictates post-login navigation.

A typical vulnerable pattern uses Flask's request.args.get('next') or request.form.get('next') without validation, then passes the value directly to redirect(). For example:

from flask import Flask, request, redirect, url_for

app = Flask(__name__)

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        # ... authentication logic ...
        next_url = request.args.get('next')  # Untrusted input
        return redirect(next_url or url_for('index'))  # VULNERABLE
    return '
...
' if __name__ == '__main__': app.run()

An attacker could send a victim a link like https://trusted-app.com/login?next=https://evil.com/phish. After login, the user is redirected to evil.com. This bypasses trust indicators since the initial domain appears legitimate. Similar risks exist in OAuth callbacks (/auth/callback?next=...), password reset flows, or any endpoint using user-controlled URLs for redirection.

Flask's flexibility with routing and request handling makes this easy to overlook, especially when developers assume the 'next' parameter is safe or only used internally. middleBrick detects such issues by scanning unauthenticated endpoints for redirect behaviors and testing with external domains to confirm exploitability.

Flask-Specific Detection

Identifying open redirects in Flask requires testing for uncontrolled URL redirection in responses to requests containing parameters like next, redirect_url, url, return_to, or continue. middleBrick performs active black-box scanning by injecting test URLs (e.g., https://example.com.malicious.test) into these parameters and analyzing HTTP responses for Location headers or JavaScript redirects pointing to the test domain.

For instance, given a Flask route:

@app.route('/oauth/callback')
def oauth_callback():
    # ... OAuth token exchange ...
    redirect_to = request.args.get('state')  # Sometimes misused for redirect
    return redirect(redirect_to)

middleBrick would scan /oauth/callback?state=https://evil.com and verify if the response redirects to evil.com. It also checks for subtle bypasses like URL encoding (%2F%2Fevil.com), double encoding, or use of // to exploit protocol-relative URLs.

Detection includes verifying:

  • HTTP 302/307/308 responses with malicious Location headers
  • HTML/JavaScript redirects (window.location.href) in 200 OK bodies
  • Open redirect via Flask's url_for() if external domains are mistakenly allowed (though url_for() is generally safe for internal routes)
  • Unlike source scanners, middleBrick requires no access to Flask source code or configuration. It tests the live, unauthenticated attack surface, making it effective for detecting runtime misconfigurations in deployed Flask APIs—even those behind proxies or load balancers—without agents or credentials.

Flask-Specific Remediation

Fixing open redirects in Flask involves validating and sanitizing user-supplied redirect URLs before using them in redirect(). The safest approach is to avoid external redirects entirely; if required, implement strict allowlisting of trusted domains or relative paths.

Use Flask's url_for() for internal routes only. For external redirects, validate the URL against a list of allowed hosts. Example fix for the login route:

from flask import Flask, request, redirect, url_for
from urllib.parse import urlparse

app = Flask(__name__)

ALLOWED_REDIRECT_HOSTS = {'trusted-app.com', 'api.trusted-app.com'}

def is_safe_url(target):
    ref_url = urlparse(request.host_url)
    test_url = urlparse(urlparse(request.host_url).scheme + '://' + target.netloc)
    return test_url.scheme in ('http', 'https') and \
           ref_url.netloc == test_url.netloc

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        # ... authenticate user ...
        next_url = request.args.get('next')
        if not next_url or not is_safe_url(next_url):
            next_url = url_for('index')
        return redirect(next_url)
    return '
...
' if __name__ == '__main__': app.run()

The is_safe_url() function ensures the redirect target matches the current host or is in an allowlist. Alternative approaches:

  • Use werkzeug.urls.url_parse to check netloc against a set of allowed domains.
  • For OAuth flows, use the state parameter for CSRF protection only—never for redirect URLs. Store the intended redirect in the user session instead.
  • Leverage Flask extensions like Flask-Security or Flask-Login that provide safe redirect helpers (e.g., Flask-Login's login_user() with remember and safe next handling).
  • After fixing, rescan with middleBrick to confirm the vulnerability is resolved. The tool will report improved security scores and remediation guidance in its dashboard, CLI, or GitHub Action output, helping teams maintain secure Flask APIs throughout development and deployment.

Frequently Asked Questions

Can middleBrick detect open redirects in Flask applications that use JavaScript-based redirects (e.g., window.location.href)?
Yes. middleBrick scans both HTTP response headers (Location) and response bodies for JavaScript redirects. It analyzes HTML and JavaScript content for patterns like window.location.href = ' followed by user-controlled input, confirming exploitability regardless of redirect mechanism.
Is it safe to use Flask's <code>url_for()</code> with user input to prevent open redirects?
Only if the user input refers to an endpoint defined in your Flask app. url_for() safely generates URLs for internal routes but will raise an error (BuildError) for undefined endpoints. Never pass unsanitized user input directly to url_for() as the endpoint name—validate it against a list of allowed endpoints first.