Compare commits

..

2 Commits
2.0.7 ... 2.0.8

Author SHA1 Message Date
Tim Abbott
726ab9c4fa Release Zulip Server 2.0.8. 2019-12-12 17:15:53 -08:00
Anders Kaseorg
b7c87a4d82 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>
2019-12-12 17:14:00 -08:00
6 changed files with 32 additions and 13 deletions

View File

@@ -52,7 +52,7 @@ author = 'The Zulip Team'
# The short X.Y version.
version = '2.0'
# The full version, including alpha/beta/rc tags.
release = '2.0.7'
release = '2.0.8'
# This allows us to insert a warning that appears only on an unreleased
# version, e.g. to say that something is likely to have changed.

View File

@@ -7,6 +7,10 @@ All notable changes to the Zulip server are documented in this file.
This section lists notable unreleased changes; it is generally updated
in bursts.
### 2.0.8 -- 2019-12-12
- CVE-2019-19775: Close open redirect in thumbnail view.
### 2.0.7 -- 2019-11-21
- CVE-2019-18933: Fix insecure account creation via social authentication.

View File

@@ -1,6 +1,6 @@
ZULIP_VERSION = "2.0.7"
ZULIP_VERSION = "2.0.8"
LATEST_MAJOR_VERSION = "2.0"
LATEST_RELEASE_VERSION = "2.0.7"
LATEST_RELEASE_VERSION = "2.0.8"
LATEST_RELEASE_ANNOUNCEMENT = "https://blog.zulip.org/2019/03/01/zulip-2-0-released/"
# Bump the minor PROVISION_VERSION to indicate that folks should provision

View File

@@ -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))

View File

@@ -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')

View File

@@ -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!")