Flask Open Redirect

MEDIUM

User input from Flask request parameters flows to redirect() without URL validation. Validate redirect targets against an allowlist or use url_for() to generate trusted application-internal URLs.

Rule Information

Language
Python
Category
Flask
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythonflaskopen-redirectunvalidated-redirectphishingcross-fileinter-proceduraltaint-analysisCWE-601OWASP-A01
CWE References

Interactive Playground

Experiment with the vulnerable code and security rule below. Edit the code to see how the rule detects different vulnerability patterns.

pathfinder scan --ruleset python/PYTHON-FLASK-SEC-012 --project .
1
2
3
4
5
6
7
8
9
10
11
12
13
rule.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

About This Rule

Understanding the vulnerability and how it is detected

This rule detects open redirect vulnerabilities in Flask applications where user-controlled input from HTTP request parameters flows to Flask's redirect() function without URL validation. An open redirect occurs when the application accepts a URL or path from user input and issues an HTTP redirect (302 or 301) to that destination without verifying it belongs to a trusted origin.

Open redirects are endemic in Flask login flows: after authentication, the application redirects to the "next" URL parameter. Without validation, an attacker sends victims to a URL like: https://legitimate-app.com/login?next=https://evil.com/phishing

The victim sees the legitimate application's login page, authenticates, and is transparently redirected to the attacker's site -- all appearing as a natural post-login navigation. This is a primary vector for credential phishing because the legitimate domain is visible in the initial URL shared with the victim.

The taint analysis traces the URL value from Flask request sources through variable assignments and function calls to the Flask redirect() function at argument position 0. Flows through url_for() or url_has_allowed_host_and_scheme() are recognized as sanitizers: url_for() generates only application-internal URLs, and url_has_allowed_host_and_scheme() (from Werkzeug) validates that a URL's host and scheme are acceptable.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Phishing Attack Amplification via Trusted Domain

Attackers craft URLs like https://yourapp.com/login?next=https://phishing.com/steal and distribute them. Victims who click the link land on the real application login page, authenticate normally, and are redirected to the phishing site. Because the initial URL shows the legitimate domain, anti-phishing training does not help. The application's credibility is weaponized against its own users.

2

OAuth Token Theft via Redirect URI Manipulation

In OAuth flows, a vulnerable redirect_uri parameter in the authorization request can redirect access tokens to an attacker-controlled site. If the Flask application uses the "next" parameter in OAuth callbacks without validation, the access token or authorization code in the redirect URL is sent to the attacker's server.

3

SSRF Gadget in Redirect Chains

Internal services that trust redirects from the application server may follow a redirect to an internal endpoint. In microservice architectures, an open redirect in a Flask service can be chained with redirect-following HTTP clients in other services to reach internal APIs that the attacker cannot access directly.

4

Session Fixation via External Redirect with Cookie Setting

An attacker who controls the redirect destination can combine the open redirect with a response that sets a cookie or manipulates browser storage, then redirects back to the application with a known session token (session fixation). The redirect through a legitimate domain gives the attacker's cookies the opportunity to be stored.

How to Fix

Recommended remediation steps

  • 1For post-login and post-action redirects, use url_for() with a route name rather than accepting a redirect URL from the request -- url_for() can only generate URLs for routes defined in the application.
  • 2If you must accept a redirect URL from user input, validate it with werkzeug's url_has_allowed_host_and_scheme() which checks that the URL's host matches the application's configured allowed hosts.
  • 3Maintain an explicit allowlist of external domains that the application is permitted to redirect to, and reject any redirect URL whose host is not on the list.
  • 4Implement a redirect disclaimer page for redirects to external sites -- show users where they are going and require confirmation before navigating away from the application.
  • 5Log all redirects to external domains for anomaly detection -- legitimate use rarely generates high volumes of redirects to diverse external domains.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

Scope: global (cross-file taint tracking across the entire project). Sources: Flask HTTP input methods -- request.args.get(), request.form.get(), request.values.get(), request.get_json() -- which can deliver attacker-controlled URL strings. The "next" parameter in login flows is the most common source. Sinks: Flask's redirect() function. The .tracks(0) parameter targets the URL argument at position 0 -- the redirect destination. The HTTP status code argument at position 1 is not tracked. Sanitizers: url_for() and url_has_allowed_host_and_scheme() are recognized as sanitizing transformations. url_for() generates only application-internal URLs (it cannot produce external URLs). url_has_allowed_host_and_scheme() from Werkzeug validates that the host and scheme are in the application's allowed set. A redirect URL that passes through either is treated as safe. The rule follows URL values through variable assignments, string manipulation, and cross-file function calls to the redirect() sink.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

OWASP Top 10
A01:2021 - Broken Access Control (unvalidated redirects are an access control failure)
CWE Top 25
CWE-601 -- URL Redirection to Untrusted Site
PCI DSS v4.0
Requirement 6.2.4 -- prevent unvalidated redirects that could expose cardholder data
NIST SP 800-53
SI-10: Information Input Validation -- validate redirect target URLs
ASVS v4.0
V5.1.5 -- verify that redirect and forward parameters are not included in allowlisted redirect targets

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Flask Open Redirect

Yes, though it is lower severity than injection or authentication bypass. Open redirect is primarily a phishing amplifier -- it allows attackers to use the legitimate application's domain in URLs that lead to malicious sites. Its real-world impact is measured by the credibility of the domain and the size of the user base. High- trust applications (banking, healthcare, government) have significantly higher phishing risk from open redirects.
url_for() takes a Flask endpoint name (e.g., 'dashboard', 'user.profile') and generates the URL for that route. It can only produce URLs that correspond to routes registered in the application. It cannot generate external URLs like https://evil.com. Using url_for() as the redirect target guarantees the destination is an internal application route.
url_has_allowed_host_and_scheme(url, allowed_hosts) parses the URL and checks that the hostname is in the allowed_hosts set and the scheme is http or https. For relative URLs (no hostname), it returns True. Pass request.host as the allowed host to permit redirects to the same application domain only.
The rule tracks whether the redirect destination value is tainted from user input. A hardcoded redirect like redirect('https://app.example.com/dashboard') has no taint and will not trigger. Only redirects where the URL or a part of it traces back to a Flask request source are flagged.
A starts-with check on a string like 'https://yourapp.com' can be bypassed with URLs like 'https://yourapp.com.evil.com' (if not checking the full hostname) or 'https://yourapp.com@evil.com' (attacker.com becomes the actual host with yourapp.com as the userinfo). Use url_has_allowed_host_and_scheme() which parses the URL properly and compares the actual netloc (hostname:port) component.
Store the intended destination in the server-side session before the OAuth flow begins: session['next'] = request.args.get('next'). After OAuth completion, retrieve it from the session and validate it with url_has_allowed_host_and_scheme() before redirecting. Never pass the "next" URL through the OAuth provider -- it becomes an open redirect in the OAuth callback.
No. url_for() is recognized as a sanitizer. redirect(url_for('dashboard')) does not trigger the rule because the URL is generated by url_for(), not from a tainted source. The rule only fires when the argument to redirect() traces back to a Flask request parameter without passing through a recognized sanitizer.

New feature

Get these findings posted directly on your GitHub pull requests

The Flask Open Redirect rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.

See how it works