RSTR-RDR-002 — Flask / Django redirect with request input
Summary
A Python web handler redirects the browser to a URL taken directly
from request arguments. An attacker can craft a link such as
https://yoursite/login?next=https://phish.example — the victim sees
a trusted-looking link, clicks, and lands on the attacker's page after
the redirect.
Severity
Medium. Open-redirects amplify phishing and frequently chain into
OAuth-flow takeovers when the redirect target receives a token.
Languages
Python (Flask, Django).
What rastray flags
@app.route('/post-login')
def post_login():
return redirect(request.args['next']) # ← flagged
def view(request):
return HttpResponseRedirect(request.GET['next']) # ← flagged
What rastray deliberately does not flag
redirect(url_for('some_view'))(named URLs).- Redirects to literal strings.
- Redirects passed through
urlsafe_allowed_hosts(...)or any helper that the rule cannot resolve.
How to fix it
Validate the redirect target against an allow-list (relative paths or trusted hosts):
from urllib.parse import urlparse
ALLOWED_HOSTS = {'app.example.com', 'admin.example.com'}
def safe_redirect_target(raw: str) -> str:
parsed = urlparse(raw)
if not parsed.netloc:
return raw # relative path — safe
if parsed.netloc in ALLOWED_HOSTS:
return raw # trusted external host
return '/' # fall back to home
@app.route('/post-login')
def post_login():
return redirect(safe_redirect_target(request.args.get('next', '/')))
Django ships an equivalent helper out of the box —
django.utils.http.url_has_allowed_host_and_scheme — use it on every
next= parameter.