Skip to content

Commit

Permalink
[2.2.X] Fixed CVE-2019-14232 -- Adjusted regex to avoid backtracking …
Browse files Browse the repository at this point in the history
…issues when truncating HTML.

Thanks to Guido Vranken for initial report.
  • Loading branch information
apollo13 authored and carltongibson committed Jul 29, 2019
1 parent f9462f4 commit c328971
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 8 deletions.
4 changes: 2 additions & 2 deletions django/utils/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ def capfirst(x):


# Set up regular expressions
re_words = re.compile(r'<.*?>|((?:\w[-\w]*|&.*?;)+)', re.S)
re_chars = re.compile(r'<.*?>|(.)', re.S)
re_words = re.compile(r'<[^>]+?>|([^<>\s]+)', re.S)
re_chars = re.compile(r'<[^>]+?>|(.)', re.S)
re_tag = re.compile(r'<(/)?(\S+?)(?:(\s*/)|\s.*?)?>', re.S)
re_newlines = re.compile(r'\r\n|\r') # Used in normalize_newlines
re_camel_case = re.compile(r'(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))')
Expand Down
14 changes: 14 additions & 0 deletions docs/releases/1.11.23.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,17 @@ Django 1.11.23 release notes
*August 1, 2019*

Django 1.11.23 fixes security issues in 1.11.22.

CVE-2019-14232: Denial-of-service possibility in ``django.utils.text.Truncator``
================================================================================

If ``django.utils.text.Truncator``'s ``chars()`` and ``words()`` methods
were passed the ``html=True`` argument, they were extremely slow to evaluate
certain inputs due to a catastrophic backtracking vulnerability in a regular
expression. The ``chars()`` and ``words()`` methods are used to implement the
:tfilter:`truncatechars_html` and :tfilter:`truncatewords_html` template
filters, which were thus vulnerable.

The regular expressions used by ``Truncator`` have been simplified in order to
avoid potential backtracking issues. As a consequence, trailing punctuation may
now at times be included in the truncated output.
14 changes: 14 additions & 0 deletions docs/releases/2.1.11.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,17 @@ Django 2.1.11 release notes
*August 1, 2019*

Django 2.1.11 fixes security issues in 2.1.10.

CVE-2019-14232: Denial-of-service possibility in ``django.utils.text.Truncator``
================================================================================

If ``django.utils.text.Truncator``'s ``chars()`` and ``words()`` methods
were passed the ``html=True`` argument, they were extremely slow to evaluate
certain inputs due to a catastrophic backtracking vulnerability in a regular
expression. The ``chars()`` and ``words()`` methods are used to implement the
:tfilter:`truncatechars_html` and :tfilter:`truncatewords_html` template
filters, which were thus vulnerable.

The regular expressions used by ``Truncator`` have been simplified in order to
avoid potential backtracking issues. As a consequence, trailing punctuation may
now at times be included in the truncated output.
14 changes: 14 additions & 0 deletions docs/releases/2.2.4.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ Django 2.2.4 release notes

Django 2.2.4 fixes security issues and several bugs in 2.2.3.

CVE-2019-14232: Denial-of-service possibility in ``django.utils.text.Truncator``
================================================================================

If ``django.utils.text.Truncator``'s ``chars()`` and ``words()`` methods
were passed the ``html=True`` argument, they were extremely slow to evaluate
certain inputs due to a catastrophic backtracking vulnerability in a regular
expression. The ``chars()`` and ``words()`` methods are used to implement the
:tfilter:`truncatechars_html` and :tfilter:`truncatewords_html` template
filters, which were thus vulnerable.

The regular expressions used by ``Truncator`` have been simplified in order to
avoid potential backtracking issues. As a consequence, trailing punctuation may
now at times be included in the truncated output.

Bugfixes
========

Expand Down
4 changes: 2 additions & 2 deletions tests/template_tests/filter_tests/test_truncatewords_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ def test_truncate(self):
def test_truncate2(self):
self.assertEqual(
truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 4),
'<p>one <a href="#">two - three <br>four …</a></p>',
'<p>one <a href="#">two - three …</a></p>',
)

def test_truncate3(self):
self.assertEqual(
truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 5),
'<p>one <a href="#">two - three <br>four</a> five</p>',
'<p>one <a href="#">two - three <br>four</a></p>',
)

def test_truncate4(self):
Expand Down
25 changes: 21 additions & 4 deletions tests/utils_tests/test_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@ def test_truncate_chars(self):
# lazy strings are handled correctly
self.assertEqual(text.Truncator(lazystr('The quick brown fox')).chars(10), 'The quick…')

def test_truncate_chars_html(self):
perf_test_values = [
(('</a' + '\t' * 50000) + '//>', None),
('&' * 50000, '&' * 9 + '…'),
('_X<<<<<<<<<<<>', None),
]
for value, expected in perf_test_values:
with self.subTest(value=value):
truncator = text.Truncator(value)
self.assertEqual(expected if expected else value, truncator.chars(10, html=True))

def test_truncate_words(self):
truncator = text.Truncator('The quick brown fox jumped over the lazy dog.')
self.assertEqual('The quick brown fox jumped over the lazy dog.', truncator.words(10))
Expand Down Expand Up @@ -135,11 +146,17 @@ def test_truncate_html_words(self):
truncator = text.Truncator('<i>Buenos d&iacute;as! &#x00bf;C&oacute;mo est&aacute;?</i>')
self.assertEqual('<i>Buenos d&iacute;as! &#x00bf;C&oacute;mo…</i>', truncator.words(3, html=True))
truncator = text.Truncator('<p>I &lt;3 python, what about you?</p>')
self.assertEqual('<p>I &lt;3 python…</p>', truncator.words(3, html=True))
self.assertEqual('<p>I &lt;3 python,…</p>', truncator.words(3, html=True))

re_tag_catastrophic_test = ('</a' + '\t' * 50000) + '//>'
truncator = text.Truncator(re_tag_catastrophic_test)
self.assertEqual(re_tag_catastrophic_test, truncator.words(500, html=True))
perf_test_values = [
('</a' + '\t' * 50000) + '//>',
'&' * 50000,
'_X<<<<<<<<<<<>',
]
for value in perf_test_values:
with self.subTest(value=value):
truncator = text.Truncator(value)
self.assertEqual(value, truncator.words(50, html=True))

def test_wrap(self):
digits = '1234 67 9'
Expand Down

0 comments on commit c328971

Please sign in to comment.