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.

References