diff options
author | Soumya Sambu <soumya.sambu@windriver.com> | 2025-01-10 13:17:53 +0000 |
---|---|---|
committer | Armin Kuster <akuster808@gmail.com> | 2025-01-22 19:20:02 -0500 |
commit | 580693f8b9e0e27ba984d83e3e4b4b8a749b8480 (patch) | |
tree | c6a8f7ee7bc81aeb9b6bd2b537b27d6f8c8953d0 /meta-python/recipes-devtools/python/python3-django | |
parent | ff5e933e58384b180a339fb8808478db1ff4ade7 (diff) | |
download | meta-openembedded-580693f8b9e0e27ba984d83e3e4b4b8a749b8480.tar.gz |
python3-django: Fix CVE-2024-38875
An issue was discovered in Django 4.2 before 4.2.14 and 5.0 before 5.0.7.
urlize and urlizetrunc were subject to a potential denial of service attack
via certain inputs with a very large number of brackets.
References:
https://nvd.nist.gov/vuln/detail/CVE-2024-38875
https://github.com/advisories/GHSA-qg2p-9jwr-mmqf
Upstream-patch:
https://github.com/django/django/commit/79f368764295df109a37192f6182fb6f361d85b5
Signed-off-by: Soumya Sambu <soumya.sambu@windriver.com>
Signed-off-by: Armin Kuster <akuster808@gmail.com>
Diffstat (limited to 'meta-python/recipes-devtools/python/python3-django')
-rw-r--r-- | meta-python/recipes-devtools/python/python3-django/CVE-2024-38875.patch | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/meta-python/recipes-devtools/python/python3-django/CVE-2024-38875.patch b/meta-python/recipes-devtools/python/python3-django/CVE-2024-38875.patch new file mode 100644 index 0000000000..8ccb888c60 --- /dev/null +++ b/meta-python/recipes-devtools/python/python3-django/CVE-2024-38875.patch | |||
@@ -0,0 +1,161 @@ | |||
1 | From 79f368764295df109a37192f6182fb6f361d85b5 Mon Sep 17 00:00:00 2001 | ||
2 | From: Adam Johnson <me@adamj.eu> | ||
3 | Date: Mon, 24 Jun 2024 15:30:59 +0200 | ||
4 | Subject: [PATCH] [4.2.x] Fixed CVE-2024-38875 -- Mitigated potential DoS in | ||
5 | urlize and urlizetrunc template filters. | ||
6 | |||
7 | Thank you to Elias Myllymäki for the report. | ||
8 | |||
9 | Co-authored-by: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> | ||
10 | |||
11 | CVE: CVE-2024-38875 | ||
12 | |||
13 | Upstream-Status: Backport [https://github.com/django/django/commit/79f368764295df109a37192f6182fb6f361d85b5] | ||
14 | |||
15 | Signed-off-by: Soumya Sambu <soumya.sambu@windriver.com> | ||
16 | --- | ||
17 | django/utils/html.py | 90 +++++++++++++++++++++++++--------- | ||
18 | tests/utils_tests/test_html.py | 7 +++ | ||
19 | 2 files changed, 73 insertions(+), 21 deletions(-) | ||
20 | |||
21 | diff --git a/django/utils/html.py b/django/utils/html.py | ||
22 | index 7a33d5f..f1b74ab 100644 | ||
23 | --- a/django/utils/html.py | ||
24 | +++ b/django/utils/html.py | ||
25 | @@ -234,6 +234,15 @@ def smart_urlquote(url): | ||
26 | |||
27 | return urlunsplit((scheme, netloc, path, query, fragment)) | ||
28 | |||
29 | +class CountsDict(dict): | ||
30 | + def __init__(self, *args, word, **kwargs): | ||
31 | + super().__init__(*args, *kwargs) | ||
32 | + self.word = word | ||
33 | + | ||
34 | + def __missing__(self, key): | ||
35 | + self[key] = self.word.count(key) | ||
36 | + return self[key] | ||
37 | + | ||
38 | |||
39 | @keep_lazy_text | ||
40 | def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False): | ||
41 | @@ -268,36 +277,69 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False): | ||
42 | return text.replace('&', '&').replace('<', '<').replace( | ||
43 | '>', '>').replace('"', '"').replace(''', "'") | ||
44 | |||
45 | - def trim_punctuation(lead, middle, trail): | ||
46 | + def wrapping_punctuation_openings(): | ||
47 | + return "".join(dict(WRAPPING_PUNCTUATION).keys()) | ||
48 | + | ||
49 | + def trailing_punctuation_chars_no_semicolon(): | ||
50 | + return TRAILING_PUNCTUATION_CHARS.replace(";", "") | ||
51 | + | ||
52 | + def trailing_punctuation_chars_has_semicolon(): | ||
53 | + return ";" in TRAILING_PUNCTUATION_CHARS | ||
54 | + | ||
55 | + def trim_punctuation(word): | ||
56 | """ | ||
57 | Trim trailing and wrapping punctuation from `middle`. Return the items | ||
58 | of the new state. | ||
59 | """ | ||
60 | + # Strip all opening wrapping punctuation. | ||
61 | + middle = word.lstrip(wrapping_punctuation_openings()) | ||
62 | + lead = word[: len(word) - len(middle)] | ||
63 | + trail = "" | ||
64 | + | ||
65 | # Continue trimming until middle remains unchanged. | ||
66 | trimmed_something = True | ||
67 | - while trimmed_something: | ||
68 | + counts = CountsDict(word=middle) | ||
69 | + while trimmed_something and middle: | ||
70 | trimmed_something = False | ||
71 | # Trim wrapping punctuation. | ||
72 | for opening, closing in WRAPPING_PUNCTUATION: | ||
73 | - if middle.startswith(opening): | ||
74 | - middle = middle[len(opening):] | ||
75 | - lead += opening | ||
76 | - trimmed_something = True | ||
77 | - # Keep parentheses at the end only if they're balanced. | ||
78 | - if (middle.endswith(closing) and | ||
79 | - middle.count(closing) == middle.count(opening) + 1): | ||
80 | - middle = middle[:-len(closing)] | ||
81 | - trail = closing + trail | ||
82 | - trimmed_something = True | ||
83 | - # Trim trailing punctuation (after trimming wrapping punctuation, | ||
84 | - # as encoded entities contain ';'). Unescape entites to avoid | ||
85 | - # breaking them by removing ';'. | ||
86 | - middle_unescaped = unescape(middle) | ||
87 | - stripped = middle_unescaped.rstrip(TRAILING_PUNCTUATION_CHARS) | ||
88 | - if middle_unescaped != stripped: | ||
89 | - trail = middle[len(stripped):] + trail | ||
90 | - middle = middle[:len(stripped) - len(middle_unescaped)] | ||
91 | + if counts[opening] < counts[closing]: | ||
92 | + rstripped = middle.rstrip(closing) | ||
93 | + if rstripped != middle: | ||
94 | + strip = counts[closing] - counts[opening] | ||
95 | + trail = middle[-strip:] | ||
96 | + middle = middle[:-strip] | ||
97 | + trimmed_something = True | ||
98 | + counts[closing] -= strip | ||
99 | + | ||
100 | + rstripped = middle.rstrip(trailing_punctuation_chars_no_semicolon()) | ||
101 | + if rstripped != middle: | ||
102 | + trail = middle[len(rstripped) :] + trail | ||
103 | + middle = rstripped | ||
104 | trimmed_something = True | ||
105 | + | ||
106 | + if trailing_punctuation_chars_has_semicolon() and middle.endswith(";"): | ||
107 | + # Only strip if not part of an HTML entity. | ||
108 | + amp = middle.rfind("&") | ||
109 | + if amp == -1: | ||
110 | + can_strip = True | ||
111 | + else: | ||
112 | + potential_entity = middle[amp:] | ||
113 | + escaped = unescape(potential_entity) | ||
114 | + can_strip = (escaped == potential_entity) or escaped.endswith(";") | ||
115 | + | ||
116 | + if can_strip: | ||
117 | + rstripped = middle.rstrip(";") | ||
118 | + amount_stripped = len(middle) - len(rstripped) | ||
119 | + if amp > -1 and amount_stripped > 1: | ||
120 | + # Leave a trailing semicolon as might be an entity. | ||
121 | + trail = middle[len(rstripped) + 1 :] + trail | ||
122 | + middle = rstripped + ";" | ||
123 | + else: | ||
124 | + trail = middle[len(rstripped) :] + trail | ||
125 | + middle = rstripped | ||
126 | + trimmed_something = True | ||
127 | + | ||
128 | return lead, middle, trail | ||
129 | |||
130 | def is_email_simple(value): | ||
131 | @@ -321,9 +363,7 @@ def urlize(text, trim_url_limit=None, no | ||
132 | # lead: Current punctuation trimmed from the beginning of the word. | ||
133 | # middle: Current state of the word. | ||
134 | # trail: Current punctuation trimmed from the end of the word. | ||
135 | - lead, middle, trail = '', word, '' | ||
136 | - # Deal with punctuation. | ||
137 | - lead, middle, trail = trim_punctuation(lead, middle, trail) | ||
138 | + lead, middle, trail = trim_punctuation(word) | ||
139 | |||
140 | # Make URL we want to point to. | ||
141 | url = None | ||
142 | diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py | ||
143 | index 5cc2d9b..715c1c6 100644 | ||
144 | --- a/tests/utils_tests/test_html.py | ||
145 | +++ b/tests/utils_tests/test_html.py | ||
146 | @@ -267,6 +267,13 @@ class TestUtilsHtml(SimpleTestCase): | ||
147 | 'foo@.example.com', | ||
148 | 'foo@localhost', | ||
149 | 'foo@localhost.', | ||
150 | + # trim_punctuation catastrophic tests | ||
151 | + "(" * 100_000 + ":" + ")" * 100_000, | ||
152 | + "(" * 100_000 + "&:" + ")" * 100_000, | ||
153 | + "([" * 100_000 + ":" + "])" * 100_000, | ||
154 | + "[(" * 100_000 + ":" + ")]" * 100_000, | ||
155 | + "([[" * 100_000 + ":" + "]])" * 100_000, | ||
156 | + "&:" + ";" * 100_000, | ||
157 | ) | ||
158 | for value in tests: | ||
159 | with self.subTest(value=value): | ||
160 | -- | ||
161 | 2.40.0 | ||