mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 04:52:12 +00:00
CVE-2019-19775: Close open redirect in thumbnail view.
This closes an open redirect vulnerability, one case of which was found by Graham Bleaney and Ibrahim Mohamed using Pysa. Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
This commit is contained in:
committed by
Tim Abbott
parent
1bf0a92eb6
commit
b7c87a4d82
@@ -9,6 +9,7 @@ import markdown
|
||||
import logging
|
||||
import traceback
|
||||
import urllib
|
||||
import urllib.parse
|
||||
import re
|
||||
import os
|
||||
import html
|
||||
@@ -553,7 +554,7 @@ class InlineHttpsProcessor(markdown.treeprocessors.Treeprocessor):
|
||||
found_imgs = walk_tree(root, lambda e: e if e.tag == "img" else None)
|
||||
for img in found_imgs:
|
||||
url = img.get("src")
|
||||
if not url.startswith("http://"):
|
||||
if urllib.parse.urlsplit(url).scheme != "http":
|
||||
# Don't rewrite images on our own site (e.g. emoji).
|
||||
continue
|
||||
img.set("src", get_camo_url(url))
|
||||
|
@@ -4,6 +4,7 @@ import base64
|
||||
import os
|
||||
import sys
|
||||
import urllib
|
||||
from urllib.parse import urljoin, urlsplit, urlunsplit
|
||||
from django.conf import settings
|
||||
from libthumbor import CryptoURL
|
||||
|
||||
@@ -19,7 +20,8 @@ def is_thumbor_enabled() -> bool:
|
||||
return settings.THUMBOR_URL != ''
|
||||
|
||||
def user_uploads_or_external(url: str) -> bool:
|
||||
return url.startswith('http') or url.lstrip('/').startswith('user_uploads/')
|
||||
u = urlsplit(url)
|
||||
return u.scheme != "" or u.netloc != "" or u.path.startswith("/user_uploads/")
|
||||
|
||||
def get_source_type(url: str) -> str:
|
||||
if not url.startswith('/user_uploads/'):
|
||||
@@ -33,16 +35,16 @@ def get_source_type(url: str) -> str:
|
||||
def generate_thumbnail_url(path: str,
|
||||
size: str='0x0',
|
||||
is_camo_url: bool=False) -> str:
|
||||
if not (path.startswith('https://') or path.startswith('http://')):
|
||||
path = '/' + path
|
||||
path = urljoin("/", path)
|
||||
u = urlsplit(path)
|
||||
|
||||
if not is_thumbor_enabled():
|
||||
if path.startswith('http://'):
|
||||
return get_camo_url(path)
|
||||
return path
|
||||
if u.scheme == "" and u.netloc == "":
|
||||
return urlunsplit(u)
|
||||
return get_camo_url(path)
|
||||
|
||||
if not user_uploads_or_external(path):
|
||||
return path
|
||||
if u.scheme == "" and u.netloc == "" and not u.path.startswith("/user_uploads/"):
|
||||
return urlunsplit(u)
|
||||
|
||||
source_type = get_source_type(path)
|
||||
safe_url = base64.urlsafe_b64encode(path.encode()).decode('utf-8')
|
||||
|
@@ -148,6 +148,9 @@ class ThumbnailTest(ZulipTestCase):
|
||||
image_url = 'http://images.foobar.com/12345'
|
||||
run_test_with_image_url(image_url)
|
||||
|
||||
image_url = '//images.foobar.com/12345'
|
||||
run_test_with_image_url(image_url)
|
||||
|
||||
def test_local_file_type(self) -> None:
|
||||
def get_file_path_urlpart(uri: str, size: str='') -> str:
|
||||
url_in_result = 'smart/filters:no_upscale()%s/%s/source_type/local_file'
|
||||
@@ -281,7 +284,8 @@ class ThumbnailTest(ZulipTestCase):
|
||||
with self.settings(THUMBOR_URL=''):
|
||||
result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_uri))
|
||||
self.assertEqual(result.status_code, 302, result)
|
||||
self.assertEqual(uri, result.url)
|
||||
base = 'https://external-content.zulipcdn.net/external_content/56c362a24201593891955ff526b3b412c0f9fcd2/68747470733a2f2f7777772e676f6f676c652e636f6d2f696d616765732f737270722f6c6f676f34772e706e67'
|
||||
self.assertEqual(base, result.url)
|
||||
|
||||
uri = 'http://www.google.com/images/srpr/logo4w.png'
|
||||
quoted_uri = urllib.parse.quote(uri, safe='')
|
||||
@@ -291,6 +295,14 @@ class ThumbnailTest(ZulipTestCase):
|
||||
base = 'https://external-content.zulipcdn.net/external_content/7b6552b60c635e41e8f6daeb36d88afc4eabde79/687474703a2f2f7777772e676f6f676c652e636f6d2f696d616765732f737270722f6c6f676f34772e706e67'
|
||||
self.assertEqual(base, result.url)
|
||||
|
||||
uri = '//www.google.com/images/srpr/logo4w.png'
|
||||
quoted_uri = urllib.parse.quote(uri, safe='')
|
||||
with self.settings(THUMBOR_URL=''):
|
||||
result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_uri,))
|
||||
self.assertEqual(result.status_code, 302, result)
|
||||
base = 'https://external-content.zulipcdn.net/external_content/676530cf4b101d56f56cc4a37c6ef4d4fd9b0c03/2f2f7777772e676f6f676c652e636f6d2f696d616765732f737270722f6c6f676f34772e706e67'
|
||||
self.assertEqual(base, result.url)
|
||||
|
||||
def test_with_different_THUMBOR_URL(self) -> None:
|
||||
self.login(self.example_email("hamlet"))
|
||||
fp = StringIO("zulip!")
|
||||
|
Reference in New Issue
Block a user