VYPR
High severityNVD Advisory· Published Jul 11, 2024· Updated Aug 2, 2024

Wagtail regular expression denial-of-service via search query parsing

CVE-2024-39317

Description

Wagtail is an open source content management system built on Django. A bug in Wagtail's parse_query_string would result in it taking a long time to process suitably crafted inputs. When used to parse sufficiently long strings of characters without a space, parse_query_string would take an unexpectedly large amount of time to process, resulting in a denial of service. In an initial Wagtail installation, the vulnerability can be exploited by any Wagtail admin user. It cannot be exploited by end users. If your Wagtail site has a custom search implementation which uses parse_query_string, it may be exploitable by other users (e.g. unauthenticated users). Patched versions have been released as Wagtail 5.2.6, 6.0.6 and 6.1.3.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
wagtailPyPI
>= 6.0, < 6.0.66.0.6
wagtailPyPI
>= 6.1, < 6.1.36.1.3
wagtailPyPI
>= 2.0, < 5.2.65.2.6

Affected products

1

Patches

6
31b1e8532dfb

Require word boundaries before search query filters (CVE-2024-39317)

https://github.com/wagtail/wagtailJake HowardJun 27, 2024via ghsa
2 files changed · +30 12
  • wagtail/search/tests/test_queries.py+24 0 modified
    @@ -258,6 +258,30 @@ def test_phrase_with_filter(self):
             self.assertDictEqual(filters.dict(), {"author": "foo bar", "bar": "beer"})
             self.assertEqual(repr(query), repr(Phrase("hello world")))
     
    +    def test_long_queries(self):
    +        filters, query = parse_query_string("0" * 60_000)
    +        self.assertEqual(filters.dict(), {})
    +        self.assertEqual(repr(query), repr(PlainText("0" * 60_000)))
    +
    +        filters, _ = parse_query_string(f'{"a" * 60_000}:"foo bar"')
    +        self.assertEqual(filters.dict(), {"a" * 60_000: "foo bar"})
    +
    +    def test_long_filter_value(self):
    +        filters, _ = parse_query_string(f'foo:ba{"r" * 60_000}')
    +        self.assertEqual(filters.dict(), {"foo": f"ba{"r" * 60_000}"})
    +
    +    def test_joined_filters(self):
    +        filters, query = parse_query_string("foo:bar:baz")
    +        self.assertEqual(filters.dict(), {"foo": "bar"})
    +        self.assertEqual(repr(query), repr(PlainText(":baz")))
    +
    +        filters, query = parse_query_string("foo:'bar':baz")
    +        self.assertEqual(filters.dict(), {"foo": "bar"})
    +        self.assertEqual(repr(query), repr(PlainText(":baz")))
    +
    +        filters, query = parse_query_string("foo:'bar:baz'")
    +        self.assertEqual(filters.dict(), {"foo": "bar:baz"})
    +
         def test_multiple_phrases(self):
             filters, query = parse_query_string('"hello world" "hi earth"')
     
    
  • wagtail/search/utils.py+6 12 modified
    @@ -69,6 +69,8 @@ def balanced_reduce(operator, seq, initializer=NOT_SET):
     
     MAX_QUERY_STRING_LENGTH = 255
     
    +filters_regexp = re.compile(r'\b(\w+):(\w+|"[^"]+"|\'[^\']+\')')
    +
     
     def normalise_query_string(query_string):
         # Truncate query string
    @@ -83,20 +85,12 @@ def normalise_query_string(query_string):
     
     
     def separate_filters_from_query(query_string):
    -    filters_regexp = r'(\w+):(\w+|"[^"]+"|\'[^\']+\')'
    -
         filters = QueryDict(mutable=True)
    -    for match_object in re.finditer(filters_regexp, query_string):
    +    for match_object in filters_regexp.finditer(query_string):
             key, value = match_object.groups()
    -        filters.update(
    -            {
    -                key: value.strip('"')
    -                if value.strip('"') is not value
    -                else value.strip("'")
    -            }
    -        )
    -
    -    query_string = re.sub(filters_regexp, "", query_string).strip()
    +        filters.update({key: value.strip("\"'")})
    +
    +    query_string = filters_regexp.sub("", query_string).strip()
     
         return filters, query_string
     
    
b783c096b6d4

Require word boundaries before search query filters (CVE-2024-39317)

https://github.com/wagtail/wagtailJake HowardJun 27, 2024via ghsa
2 files changed · +30 12
  • wagtail/search/tests/test_queries.py+24 0 modified
    @@ -171,6 +171,30 @@ def test_phrase_with_filter(self):
             self.assertDictEqual(filters.dict(), {"author": "foo bar", "bar": "beer"})
             self.assertEqual(repr(query), repr(Phrase("hello world")))
     
    +    def test_long_queries(self):
    +        filters, query = parse_query_string("0" * 60_000)
    +        self.assertEqual(filters.dict(), {})
    +        self.assertEqual(repr(query), repr(PlainText("0" * 60_000)))
    +
    +        filters, _ = parse_query_string(f'{"a" * 60_000}:"foo bar"')
    +        self.assertEqual(filters.dict(), {"a" * 60_000: "foo bar"})
    +
    +    def test_long_filter_value(self):
    +        filters, _ = parse_query_string(f'foo:ba{"r" * 60_000}')
    +        self.assertEqual(filters.dict(), {"foo": f"ba{"r" * 60_000}"})
    +
    +    def test_joined_filters(self):
    +        filters, query = parse_query_string("foo:bar:baz")
    +        self.assertEqual(filters.dict(), {"foo": "bar"})
    +        self.assertEqual(repr(query), repr(PlainText(":baz")))
    +
    +        filters, query = parse_query_string("foo:'bar':baz")
    +        self.assertEqual(filters.dict(), {"foo": "bar"})
    +        self.assertEqual(repr(query), repr(PlainText(":baz")))
    +
    +        filters, query = parse_query_string("foo:'bar:baz'")
    +        self.assertEqual(filters.dict(), {"foo": "bar:baz"})
    +
         def test_multiple_phrases(self):
             filters, query = parse_query_string('"hello world" "hi earth"')
     
    
  • wagtail/search/utils.py+6 12 modified
    @@ -69,6 +69,8 @@ def balanced_reduce(operator, seq, initializer=NOT_SET):
     
     MAX_QUERY_STRING_LENGTH = 255
     
    +filters_regexp = re.compile(r'\b(\w+):(\w+|"[^"]+"|\'[^\']+\')')
    +
     
     def normalise_query_string(query_string):
         # Truncate query string
    @@ -83,20 +85,12 @@ def normalise_query_string(query_string):
     
     
     def separate_filters_from_query(query_string):
    -    filters_regexp = r'(\w+):(\w+|"[^"]+"|\'[^\']+\')'
    -
         filters = QueryDict(mutable=True)
    -    for match_object in re.finditer(filters_regexp, query_string):
    +    for match_object in filters_regexp.finditer(query_string):
             key, value = match_object.groups()
    -        filters.update(
    -            {
    -                key: value.strip('"')
    -                if value.strip('"') is not value
    -                else value.strip("'")
    -            }
    -        )
    -
    -    query_string = re.sub(filters_regexp, "", query_string).strip()
    +        filters.update({key: value.strip("\"'")})
    +
    +    query_string = filters_regexp.sub("", query_string).strip()
     
         return filters, query_string
     
    
3c941136f79c

Require word boundaries before search query filters (CVE-2024-39317)

https://github.com/wagtail/wagtailJake HowardJun 27, 2024via ghsa
2 files changed · +30 12
  • wagtail/search/tests/test_queries.py+24 0 modified
    @@ -171,6 +171,30 @@ def test_phrase_with_filter(self):
             self.assertDictEqual(filters.dict(), {"author": "foo bar", "bar": "beer"})
             self.assertEqual(repr(query), repr(Phrase("hello world")))
     
    +    def test_long_queries(self):
    +        filters, query = parse_query_string("0" * 60_000)
    +        self.assertEqual(filters.dict(), {})
    +        self.assertEqual(repr(query), repr(PlainText("0" * 60_000)))
    +
    +        filters, _ = parse_query_string(f'{"a" * 60_000}:"foo bar"')
    +        self.assertEqual(filters.dict(), {"a" * 60_000: "foo bar"})
    +
    +    def test_long_filter_value(self):
    +        filters, _ = parse_query_string(f'foo:ba{"r" * 60_000}')
    +        self.assertEqual(filters.dict(), {"foo": f"ba{"r" * 60_000}"})
    +
    +    def test_joined_filters(self):
    +        filters, query = parse_query_string("foo:bar:baz")
    +        self.assertEqual(filters.dict(), {"foo": "bar"})
    +        self.assertEqual(repr(query), repr(PlainText(":baz")))
    +
    +        filters, query = parse_query_string("foo:'bar':baz")
    +        self.assertEqual(filters.dict(), {"foo": "bar"})
    +        self.assertEqual(repr(query), repr(PlainText(":baz")))
    +
    +        filters, query = parse_query_string("foo:'bar:baz'")
    +        self.assertEqual(filters.dict(), {"foo": "bar:baz"})
    +
         def test_multiple_phrases(self):
             filters, query = parse_query_string('"hello world" "hi earth"')
     
    
  • wagtail/search/utils.py+6 12 modified
    @@ -69,6 +69,8 @@ def balanced_reduce(operator, seq, initializer=NOT_SET):
     
     MAX_QUERY_STRING_LENGTH = 255
     
    +filters_regexp = re.compile(r'\b(\w+):(\w+|"[^"]+"|\'[^\']+\')')
    +
     
     def normalise_query_string(query_string):
         # Truncate query string
    @@ -83,20 +85,12 @@ def normalise_query_string(query_string):
     
     
     def separate_filters_from_query(query_string):
    -    filters_regexp = r'(\w+):(\w+|"[^"]+"|\'[^\']+\')'
    -
         filters = QueryDict(mutable=True)
    -    for match_object in re.finditer(filters_regexp, query_string):
    +    for match_object in filters_regexp.finditer(query_string):
             key, value = match_object.groups()
    -        filters.update(
    -            {
    -                key: value.strip('"')
    -                if value.strip('"') is not value
    -                else value.strip("'")
    -            }
    -        )
    -
    -    query_string = re.sub(filters_regexp, "", query_string).strip()
    +        filters.update({key: value.strip("\"'")})
    +
    +    query_string = filters_regexp.sub("", query_string).strip()
     
         return filters, query_string
     
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.