mattupstate/flask-security — pull request #318 by boydgreenfield (2014-09-26 18:21:14+00:00)
Title: Prevent open redirects when a malformed URL is passed to ?next=

Example: "/login?next=http:///google.com" (note 3rd slash)

This is due to the way `urlparse.urlsplit` splits URLs. Specifically:
```python
# URL with only a path (no scheme, no netloc)
urlparse.urlsplit("/index.html")
SplitResult(scheme='', netloc='', path='/index.html', query='', fragment='')

# Standard URL has a scheme and a netloc
urlparse.urlsplit("http://www.google.com")
SplitResult(scheme='http', netloc='www.google.com', path='', query='', fragment='')

# Malformed URL has no netloc, but has a scheme
urlparse.urlsplit("ftp:///evil.com")
SplitResult(scheme='ftp', netloc='', path='/evil.com', query='', fragment='')
```

Consequently, in the old `validate_redirect_url` function, which did this:
```python
def validate_redirect_url(url):
if url is None or url.strip() == '':
return False
url_next = urlsplit(url)
url_base = urlsplit(request.host_url)
if url_next.netloc and url_next.netloc != url_base.netloc:
return False
return True
```

This condition would not be met for malformed URLs and it would return `True` (valid URL).

The included patch changes the code to return `False` if the `url_next` variable has a scheme and `url_next.netloc != url_base.netloc` (it's insufficient to simply check if `url_next.netloc != url_base.netloc` because then relative redirects, such as `?next=/index.html` fail). The fix is simply:

```python
def validate_redirect_url(url):
if url is None or url.strip() == '':
return False
url_next = urlsplit(url)
url_base = urlsplit(request.host_url)
if url_next.scheme and url_next.netloc != url_base.netloc: # Changed line
return False
return True
```

This passes all the tests, and I can't think of any other nasty edge cases it might cause, but an alternative fix could be:
```python
def validate_redirect_url(url):
if url is None or url.strip() == '':
return False
url_next = urlsplit(url)
url_base = urlsplit(request.host_url)
if url_next.scheme and not url_next.netloc:
return False
elif url_next.netloc and url_next.netloc != url_base.netloc:
return False
return True
```

Credit to [Muhammad Talha Khan](https://www.facebook.com/MTK911) for finding this.