티스토리 수익 글 보기

티스토리 수익 글 보기

[5.2.x] Fixed CVE-2026-1285 — Mitigated potential DoS in django.util… · django/django@9f2ada8 · GitHub
Skip to content
/ django Public

Commit 9f2ada8

Browse files
nessitajacobtylerwalls
authored andcommitted
[5.2.x] Fixed CVE-2026-1285 — Mitigated potential DoS in django.utils.text.Truncator for HTML input.
The `TruncateHTMLParser` used `deque.remove()` to remove tags from the stack when processing end tags. With crafted input containing many unmatched end tags, this caused repeated full scans of the tag stack, leading to quadratic time complexity. The fix uses LIFO semantics, only removing a tag from the stack when it matches the most recently opened tag. This avoids linear scans for unmatched end tags and reduces complexity to linear time. Refs #30686 and 6ee37ad. Thanks Seokchan Yoon for the report, and Jake Howard and Jacob Walls for reviews. Backport of a33540b from main.
1 parent 17a1d64 commit 9f2ada8

File tree

4 files changed

+39
4
lines changed

4 files changed

+39
4
lines changed

django/utils/text.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,11 @@ def handle_starttag(self, tag, attrs):
126126
def handle_endtag(self, tag):
127127
if tag not in self.void_elements:
128128
self.output += f"</{tag}>"
129-
try:
130-
self.tags.remove(tag)
131-
except ValueError:
132-
pass
129+
# Remove from the stack only if the tag matches the most recently
130+
# opened tag (LIFO). This avoids O(n) linear scans for unmatched
131+
# end tags if `deque.remove()` would be called.
132+
if self.tags and self.tags[0] == tag:
133+
self.tags.popleft()
133134

134135
def handle_data(self, data):
135136
data, output = self.process(data)

docs/releases/4.2.28.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,15 @@ As a reminder, all untrusted user input should be validated before use.
4141

4242
This issue has severity "high" according to the :ref:`Django security policy
4343
<security-disclosure>`.
44+
45+
CVE-2026-1285: Potential denial-of-service vulnerability in ``django.utils.text.Truncator`` HTML methods
46+
========================================================================================================
47+
48+
``django.utils.text.Truncator.chars()`` and ``Truncator.words()`` methods (with
49+
``html=True``) and the :tfilter:`truncatechars_html` and
50+
:tfilter:`truncatewords_html` template filters were subject to a potential
51+
denial-of-service attack via certain inputs with a large number of unmatched
52+
HTML end tags, which could cause quadratic time complexity during HTML parsing.
53+
54+
This issue has severity "moderate" according to the :ref:`Django security
55+
policy <security-disclosure>`.

docs/releases/5.2.11.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,15 @@ As a reminder, all untrusted user input should be validated before use.
4141

4242
This issue has severity "high" according to the :ref:`Django security policy
4343
<security-disclosure>`.
44+
45+
CVE-2026-1285: Potential denial-of-service vulnerability in ``django.utils.text.Truncator`` HTML methods
46+
========================================================================================================
47+
48+
``django.utils.text.Truncator.chars()`` and ``Truncator.words()`` methods (with
49+
``html=True``) and the :tfilter:`truncatechars_html` and
50+
:tfilter:`truncatewords_html` template filters were subject to a potential
51+
denial-of-service attack via certain inputs with a large number of unmatched
52+
HTML end tags, which could cause quadratic time complexity during HTML parsing.
53+
54+
This issue has severity "moderate" according to the :ref:`Django security
55+
policy <security-disclosure>`.

tests/utils_tests/test_text.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,16 @@ def test_truncate_chars_html_with_html_entities(self):
202202
truncator = text.Truncator("<p>I &lt;3 python, what about you?</p>")
203203
self.assertEqual("<p>I &lt;3 python, wh…</p>", truncator.chars(16, html=True))
204204

205+
def test_truncate_chars_html_with_misnested_tags(self):
206+
# LIFO removal keeps all tags when a middle tag is closed out of order.
207+
# With <a><b><c></b>, the </b> doesn't match <c>, so all tags remain
208+
# in the stack and are properly closed at truncation.
209+
truncator = text.Truncator("<a><b><c></b>XXXX")
210+
self.assertEqual(
211+
truncator.chars(2, html=True, truncate=""),
212+
"<a><b><c></b>XX</c></b></a>",
213+
)
214+
205215
def test_truncate_words(self):
206216
truncator = text.Truncator("The quick brown fox jumped over the lazy dog.")
207217
self.assertEqual(

0 commit comments

Comments
 (0)