티스토리 수익 글 보기

티스토리 수익 글 보기

[4.2.x] Fixed CVE-2023-43665 — Mitigated potential DoS in django.uti… · django/django@be9c27c · GitHub
Skip to content

Commit be9c27c

Browse files
committed
[4.2.x] Fixed CVE-2023-43665 — Mitigated potential DoS in django.utils.text.Truncator when truncating HTML text.
Thanks Wenchao Li of Alibaba Group for the report.
1 parent 39fc3f4 commit be9c27c

File tree

6 files changed

+113
11
lines changed

6 files changed

+113
11
lines changed

django/utils/text.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,14 @@ def _generator():
6767
class Truncator(SimpleLazyObject):
6868
"""
6969
An object used to truncate text, either by characters or words.
70+
71+
When truncating HTML text (either chars or words), input will be limited to
72+
at most `MAX_LENGTH_HTML` characters.
7073
"""
7174

75+
# 5 million characters are approximately 4000 text pages or 3 web pages.
76+
MAX_LENGTH_HTML = 5_000_000
77+
7278
def __init__(self, text):
7379
super().__init__(lambda: str(text))
7480

@@ -164,6 +170,11 @@ def _truncate_html(self, length, truncate, text, truncate_len, words):
164170
if words and length <= 0:
165171
return ""
166172

173+
size_limited = False
174+
if len(text) > self.MAX_LENGTH_HTML:
175+
text = text[: self.MAX_LENGTH_HTML]
176+
size_limited = True
177+
167178
html4_singlets = (
168179
"br",
169180
"col",
@@ -220,10 +231,14 @@ def _truncate_html(self, length, truncate, text, truncate_len, words):
220231
# Add it to the start of the open tags list
221232
open_tags.insert(0, tagname)
222233

234+
truncate_text = self.add_truncation_text("", truncate)
235+
223236
if current_len <= length:
237+
if size_limited and truncate_text:
238+
text += truncate_text
224239
return text
240+
225241
out = text[:end_text_pos]
226-
truncate_text = self.add_truncation_text("", truncate)
227242
if truncate_text:
228243
out += truncate_text
229244
# Close any tags still open

docs/ref/templates/builtins.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2652,6 +2652,16 @@ If ``value`` is ``"<p>Joel is a slug</p>"``, the output will be
26522652

26532653
Newlines in the HTML content will be preserved.
26542654

2655+
.. admonition:: Size of input string
2656+
2657+
Processing large, potentially malformed HTML strings can be
2658+
resource-intensive and impact service performance. ``truncatechars_html``
2659+
limits input to the first five million characters.
2660+
2661+
.. versionchanged:: 3.2.22
2662+
2663+
In older versions, strings over five million characters were processed.
2664+
26552665
.. templatefilter:: truncatewords
26562666

26572667
``truncatewords``
@@ -2694,6 +2704,16 @@ If ``value`` is ``"<p>Joel is a slug</p>"``, the output will be
26942704

26952705
Newlines in the HTML content will be preserved.
26962706

2707+
.. admonition:: Size of input string
2708+
2709+
Processing large, potentially malformed HTML strings can be
2710+
resource-intensive and impact service performance. ``truncatewords_html``
2711+
limits input to the first five million characters.
2712+
2713+
.. versionchanged:: 3.2.22
2714+
2715+
In older versions, strings over five million characters were processed.
2716+
26972717
.. templatefilter:: unordered_list
26982718

26992719
``unordered_list``

docs/releases/3.2.22.txt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,20 @@ Django 3.2.22 release notes
66

77
Django 3.2.22 fixes a security issue with severity "moderate" in 3.2.21.
88

9-
...
9+
CVE-2023-43665: Denial-of-service possibility in ``django.utils.text.Truncator``
10+
================================================================================
11+
12+
Following the fix for :cve:`2019-14232`, the regular expressions used in the
13+
implementation of ``django.utils.text.Truncator``'s ``chars()`` and ``words()``
14+
methods (with ``html=True``) were revised and improved. However, these regular
15+
expressions still exhibited linear backtracking complexity, so when given a
16+
very long, potentially malformed HTML input, the evaluation would still be
17+
slow, leading to a potential denial of service vulnerability.
18+
19+
The ``chars()`` and ``words()`` methods are used to implement the
20+
:tfilter:`truncatechars_html` and :tfilter:`truncatewords_html` template
21+
filters, which were thus also vulnerable.
22+
23+
The input processed by ``Truncator``, when operating in HTML mode, has been
24+
limited to the first five million characters in order to avoid potential
25+
performance and memory issues.

docs/releases/4.1.12.txt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,20 @@ Django 4.1.12 release notes
66

77
Django 4.1.12 fixes a security issue with severity "moderate" in 4.1.11.
88

9-
...
9+
CVE-2023-43665: Denial-of-service possibility in ``django.utils.text.Truncator``
10+
================================================================================
11+
12+
Following the fix for :cve:`2019-14232`, the regular expressions used in the
13+
implementation of ``django.utils.text.Truncator``'s ``chars()`` and ``words()``
14+
methods (with ``html=True``) were revised and improved. However, these regular
15+
expressions still exhibited linear backtracking complexity, so when given a
16+
very long, potentially malformed HTML input, the evaluation would still be
17+
slow, leading to a potential denial of service vulnerability.
18+
19+
The ``chars()`` and ``words()`` methods are used to implement the
20+
:tfilter:`truncatechars_html` and :tfilter:`truncatewords_html` template
21+
filters, which were thus also vulnerable.
22+
23+
The input processed by ``Truncator``, when operating in HTML mode, has been
24+
limited to the first five million characters in order to avoid potential
25+
performance and memory issues.

docs/releases/4.2.6.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,24 @@ Django 4.2.6 release notes
77
Django 4.2.6 fixes a security issue with severity "moderate" and several bugs
88
in 4.2.5.
99

10+
CVE-2023-43665: Denial-of-service possibility in ``django.utils.text.Truncator``
11+
================================================================================
12+
13+
Following the fix for :cve:`2019-14232`, the regular expressions used in the
14+
implementation of ``django.utils.text.Truncator``'s ``chars()`` and ``words()``
15+
methods (with ``html=True``) were revised and improved. However, these regular
16+
expressions still exhibited linear backtracking complexity, so when given a
17+
very long, potentially malformed HTML input, the evaluation would still be
18+
slow, leading to a potential denial of service vulnerability.
19+
20+
The ``chars()`` and ``words()`` methods are used to implement the
21+
:tfilter:`truncatechars_html` and :tfilter:`truncatewords_html` template
22+
filters, which were thus also vulnerable.
23+
24+
The input processed by ``Truncator``, when operating in HTML mode, has been
25+
limited to the first five million characters in order to avoid potential
26+
performance and memory issues.
27+
1028
Bugfixes
1129
========
1230

tests/utils_tests/test_text.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
import sys
3+
from unittest.mock import patch
34

45
from django.core.exceptions import SuspiciousFileOperation
56
from django.test import SimpleTestCase
@@ -94,11 +95,17 @@ def test_truncate_chars(self):
9495
text.Truncator(lazystr("The quick brown fox")).chars(10), "The quick…"
9596
)
9697

97-
def test_truncate_chars_html(self):
98+
@patch("django.utils.text.Truncator.MAX_LENGTH_HTML", 10_000)
99+
def test_truncate_chars_html_size_limit(self):
100+
max_len = text.Truncator.MAX_LENGTH_HTML
101+
bigger_len = text.Truncator.MAX_LENGTH_HTML + 1
102+
valid_html = "<p>Joel is a slug</p>" # 14 chars
98103
perf_test_values = [
99-
(("</a" + "\t" * 50000) + "//>", None),
100-
("&" * 50000, "&" * 9 + "…"),
104+
("</a" + "\t" * (max_len - 6) + "//>", None),
105+
("</p" + "\t" * bigger_len + "//>", "</p" + "\t" * 6 + "…"),
106+
("&" * bigger_len, "&" * 9 + "…"),
101107
("_X<<<<<<<<<<<>", None),
108+
(valid_html * bigger_len, "<p>Joel is a…</p>"), # 10 chars
102109
]
103110
for value, expected in perf_test_values:
104111
with self.subTest(value=value):
@@ -176,15 +183,25 @@ def test_truncate_html_words(self):
176183
truncator = text.Truncator("<p>I &lt;3 python, what about you?</p>")
177184
self.assertEqual("<p>I &lt;3 python,…</p>", truncator.words(3, html=True))
178185

186+
@patch("django.utils.text.Truncator.MAX_LENGTH_HTML", 10_000)
187+
def test_truncate_words_html_size_limit(self):
188+
max_len = text.Truncator.MAX_LENGTH_HTML
189+
bigger_len = text.Truncator.MAX_LENGTH_HTML + 1
190+
valid_html = "<p>Joel is a slug</p>" # 4 words
179191
perf_test_values = [
180-
("</a" + "\t" * 50000) + "//>",
181-
"&" * 50000,
182-
"_X<<<<<<<<<<<>",
192+
("</a" + "\t" * (max_len - 6) + "//>", None),
193+
("</p" + "\t" * bigger_len + "//>", "</p" + "\t" * (max_len - 3) + "…"),
194+
("&" * max_len, None), # no change
195+
("&" * bigger_len, "&" * max_len + "…"),
196+
("_X<<<<<<<<<<<>", None),
197+
(valid_html * bigger_len, valid_html * 12 + "<p>Joel is…</p>"), # 50 words
183198
]
184-
for value in perf_test_values:
199+
for value, expected in perf_test_values:
185200
with self.subTest(value=value):
186201
truncator = text.Truncator(value)
187-
self.assertEqual(value, truncator.words(50, html=True))
202+
self.assertEqual(
203+
expected if expected else value, truncator.words(50, html=True)
204+
)
188205

189206
def test_wrap(self):
190207
digits = "1234 67 9"

0 commit comments

Comments
 (0)