thumbnail: Tighten fix for CVE-2019-19775 open redirect.

Due to a known but unfixed bug in the Python standard library’s
urllib.parse module (CVE-2015-2104), a crafted URL could bypass the
validation in the previous patch and still achieve an open redirect.

https://bugs.python.org/issue23505

Switch to using django.utils.http.is_safe_url, which already contains
a workaround for this bug.

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
This commit is contained in:
Anders Kaseorg 2019-12-19 17:58:53 -08:00 committed by Tim Abbott
parent ef1f6b1c33
commit 319e2231b8
1 changed files with 7 additions and 8 deletions

View File

@ -4,8 +4,9 @@ import base64
import os import os
import sys import sys
import urllib import urllib
from urllib.parse import urljoin, urlsplit, urlunsplit from urllib.parse import urljoin
from django.conf import settings from django.conf import settings
from django.utils.http import is_safe_url
from libthumbor import CryptoURL from libthumbor import CryptoURL
ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath('__file__')))) ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath('__file__'))))
@ -20,8 +21,7 @@ def is_thumbor_enabled() -> bool:
return settings.THUMBOR_URL != '' return settings.THUMBOR_URL != ''
def user_uploads_or_external(url: str) -> bool: def user_uploads_or_external(url: str) -> bool:
u = urlsplit(url) return not is_safe_url(url) or url.startswith("/user_uploads/")
return u.scheme != "" or u.netloc != "" or u.path.startswith("/user_uploads/")
def get_source_type(url: str) -> str: def get_source_type(url: str) -> str:
if not url.startswith('/user_uploads/'): if not url.startswith('/user_uploads/'):
@ -36,15 +36,14 @@ def generate_thumbnail_url(path: str,
size: str='0x0', size: str='0x0',
is_camo_url: bool=False) -> str: is_camo_url: bool=False) -> str:
path = urljoin("/", path) path = urljoin("/", path)
u = urlsplit(path)
if not is_thumbor_enabled(): if not is_thumbor_enabled():
if u.scheme == "" and u.netloc == "": if is_safe_url(path):
return urlunsplit(u) return path
return get_camo_url(path) return get_camo_url(path)
if u.scheme == "" and u.netloc == "" and not u.path.startswith("/user_uploads/"): if is_safe_url(path) and not path.startswith("/user_uploads/"):
return urlunsplit(u) return path
source_type = get_source_type(path) source_type = get_source_type(path)
safe_url = base64.urlsafe_b64encode(path.encode()).decode('utf-8') safe_url = base64.urlsafe_b64encode(path.encode()).decode('utf-8')