Skip to content

Commit 1ca63a6

Browse files
committed
[1.8.x] Fixed CVE-2018-7536 -- Fixed catastrophic backtracking in urlize and urlizetrunc template filters.
Thanks Florian Apolloner for assisting with the patch.
1 parent 10f11f2 commit 1ca63a6

File tree

3 files changed

+35
-2
lines changed

3 files changed

+35
-2
lines changed

Diff for: django/utils/html.py

+16-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
word_split_re = re.compile(r'(\s+)')
3131
simple_url_re = re.compile(r'^https?://\[?\w', re.IGNORECASE)
3232
simple_url_2_re = re.compile(r'^www\.|^(?!http)\w[^@]+\.(com|edu|gov|int|mil|net|org)($|/.*)$', re.IGNORECASE)
33-
simple_email_re = re.compile(r'^\S+@\S+\.\S+$')
3433
link_target_attribute_re = re.compile(r'(<a [^>]*?)target=[^\s>]+')
3534
html_gunk_re = re.compile(
3635
r'(?:<br clear="all">|<i><\/i>|<b><\/b>|<em><\/em>|<strong><\/strong>|'
@@ -304,6 +303,21 @@ def unescape(text, trail):
304303
trail = ''
305304
return text, unescaped, trail
306305

306+
def is_email_simple(value):
307+
"""Return True if value looks like an email address."""
308+
# An @ must be in the middle of the value.
309+
if '@' not in value or value.startswith('@') or value.endswith('@'):
310+
return False
311+
try:
312+
p1, p2 = value.split('@')
313+
except ValueError:
314+
# value contains more than one @.
315+
return False
316+
# Dot must be in p2 (e.g. example.com)
317+
if '.' not in p2 or p2.startswith('.'):
318+
return False
319+
return True
320+
307321
words = word_split_re.split(force_text(text))
308322
for i, word in enumerate(words):
309323
if '.' in word or '@' in word or ':' in word:
@@ -332,7 +346,7 @@ def unescape(text, trail):
332346
elif simple_url_2_re.match(middle):
333347
middle, middle_unescaped, trail = unescape(middle, trail)
334348
url = smart_urlquote('http://%s' % middle_unescaped)
335-
elif ':' not in middle and simple_email_re.match(middle):
349+
elif ':' not in middle and is_email_simple(middle):
336350
local, domain = middle.rsplit('@', 1)
337351
try:
338352
domain = domain.encode('idna').decode('ascii')

Diff for: docs/releases/1.8.19.txt

+11
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,14 @@ Django 1.8.19 release notes
55
*March 6, 2018*
66

77
Django 1.8.19 fixes two security issues in 1.18.18.
8+
9+
CVE-2018-7536: Denial-of-service possibility in ``urlize`` and ``urlizetrunc`` template filters
10+
===============================================================================================
11+
12+
The ``django.utils.html.urlize()`` function was extremely slow to evaluate
13+
certain inputs due to a catastrophic backtracking vulnerability in a regular
14+
expression. The ``urlize()`` function is used to implement the ``urlize`` and
15+
``urlizetrunc`` template filters, which were thus vulnerable.
16+
17+
The problematic regular expression is replaced with parsing logic that behaves
18+
similarly.

Diff for: tests/utils_tests/test_html.py

+8
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,11 @@ def test_html_safe_doesnt_define_str(self):
248248
@html.html_safe
249249
class HtmlClass(object):
250250
pass
251+
252+
def test_urlize_unchanged_inputs(self):
253+
tests = (
254+
('a' + '@a' * 50000) + 'a', # simple_email_re catastrophic test
255+
('a' + '.' * 1000000) + 'a', # trailing_punctuation catastrophic test
256+
)
257+
for value in tests:
258+
self.assertEqual(html.urlize(value), value)

0 commit comments

Comments
 (0)