Skip to content

handlers: reject protocol-relative URLs in redirect validation#5281

Open
adilburaksen wants to merge 1 commit into
google:masterfrom
adilburaksen:fix/open-redirect-protocol-relative
Open

handlers: reject protocol-relative URLs in redirect validation#5281
adilburaksen wants to merge 1 commit into
google:masterfrom
adilburaksen:fix/open-redirect-protocol-relative

Conversation

@adilburaksen
Copy link
Copy Markdown

Summary

check_redirect_url in src/appengine/handlers/base_handler.py uses
_SAFE_URL_PATTERN (based on the Closure Library SafeUrl pattern) to
validate redirect targets. The relative-URL branch of that pattern is:

[^:/?#]*(?:[/?#]|$)

[^:/?#]* can match zero characters, after which (?:[/?#]|$) matches
the leading / of //evil.com. This means the pattern accepts any
protocol-relative URL such as //attacker.example.com/steal as safe.

Browsers interpret //evil.com/path as scheme-relative (same scheme as
the current page), so a next=//evil.com/login parameter causes a
post-login redirect to the attacker's host.

Fix

Add a negative lookahead (?!//) to the relative-URL branch:

# Before
r'^(?:(?:https?|mailto|ftp):|[^:/?#]*(?:[/?#]|$))'

# After
r'^(?:(?:https?|mailto|ftp):|(?!//)[^:/?#]*(?:[/?#]|$))'

This rejects any URL that the relative branch would match but that begins
with //, while continuing to accept ordinary relative paths (/login,
relative/path) and absolute URLs with safe schemes.

Verification

>>> import re
>>> p = re.compile(r'^(?:(?:https?|mailto|ftp):|(?!//)[^:/?#]*(?:[/?#]|$))', re.I)
>>> bool(p.match('//evil.com/path'))
False
>>> bool(p.match('/login'))
True
>>> bool(p.match('https://clusterfuzz.com/'))
True

_SAFE_URL_PATTERN's relative-URL branch matched the empty string
prefix of '//evil.com', so check_redirect_url accepted
protocol-relative URLs as safe. Browsers interpret '//evil.com/path'
as 'same-scheme://evil.com/path', enabling open redirect from any
endpoint that passes the user-supplied 'next' / 'redirect' parameter
through check_redirect_url.

Add a negative lookahead (?!//) before the relative-URL branch so
that any URL beginning with '//' is rejected unless it already matched
the explicit-scheme branch (http://, https://, etc).
@adilburaksen adilburaksen requested a review from a team as a code owner May 16, 2026 18:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant