VYPR
High severityNVD Advisory· Published Apr 19, 2021· Updated Aug 3, 2024

Improper validation of URLs ('Cross-site Scripting') in Wagtail rich text fields

CVE-2021-29434

Description

Wagtail is a Django content management system. In affected versions of Wagtail, when saving the contents of a rich text field in the admin interface, Wagtail does not apply server-side checks to ensure that link URLs use a valid protocol. A malicious user with access to the admin interface could thus craft a POST request to publish content with javascript: URLs containing arbitrary code. The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin. See referenced GitHub advisory for additional details, including a workaround. Patched versions have been released as Wagtail 2.11.7 (for the LTS 2.11 branch) and Wagtail 2.12.4 (for the current 2.12 branch).

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
wagtailPyPI
< 2.11.72.11.7
wagtailPyPI
>= 2.12, < 2.12.42.12.4

Affected products

1

Patches

2
5c7a60977cba

Disallow links with unrecognised protocols in contentstate

https://github.com/wagtail/wagtailMatt WestcottApr 8, 2021via ghsa
2 files changed · +55 1
  • wagtail/admin/rich_text/converters/contentstate.py+2 1 modified
    @@ -8,6 +8,7 @@
     
     from wagtail.admin.rich_text.converters.html_to_contentstate import HtmlToContentStateHandler
     from wagtail.core.rich_text import features as feature_registry
    +from wagtail.core.whitelist import check_url
     
     
     def link_entity(props):
    @@ -21,7 +22,7 @@ def link_entity(props):
             link_props['linktype'] = 'page'
             link_props['id'] = id_
         else:
    -        link_props['href'] = props.get('url')
    +        link_props['href'] = check_url(props.get('url'))
     
         return DOM.create_element('a', link_props, props['children'])
     
    
  • wagtail/admin/tests/test_contentstate.py+53 0 modified
    @@ -825,3 +825,56 @@ def test_p_with_class(self):
                 ],
                 'entityMap': {}
             })
    +
    +
    +class TestContentStateToHtml(TestCase):
    +    def test_external_link(self):
    +        converter = ContentstateConverter(features=['link'])
    +        contentstate_json = json.dumps({
    +            'entityMap': {
    +                '0': {'mutability': 'MUTABLE', 'type': 'LINK', 'data': {'url': 'http://wagtail.io'}}
    +            },
    +            'blocks': [
    +                {
    +                    'inlineStyleRanges': [], 'text': 'an external link', 'depth': 0, 'type': 'unstyled', 'key': '00000',
    +                    'entityRanges': [{'offset': 3, 'length': 8, 'key': 0}]
    +                },
    +            ]
    +        })
    +
    +        result = converter.to_database_format(contentstate_json)
    +        self.assertEqual(result, '<p>an <a href="http://wagtail.io">external</a> link</p>')
    +
    +    def test_local_link(self):
    +        converter = ContentstateConverter(features=['link'])
    +        contentstate_json = json.dumps({
    +            'entityMap': {
    +                '0': {'mutability': 'MUTABLE', 'type': 'LINK', 'data': {'url': '/some/local/path/'}}
    +            },
    +            'blocks': [
    +                {
    +                    'inlineStyleRanges': [], 'text': 'an external link', 'depth': 0, 'type': 'unstyled', 'key': '00000',
    +                    'entityRanges': [{'offset': 3, 'length': 8, 'key': 0}]
    +                },
    +            ]
    +        })
    +
    +        result = converter.to_database_format(contentstate_json)
    +        self.assertEqual(result, '<p>an <a href="/some/local/path/">external</a> link</p>')
    +
    +    def test_reject_javascript_link(self):
    +        converter = ContentstateConverter(features=['link'])
    +        contentstate_json = json.dumps({
    +            'entityMap': {
    +                '0': {'mutability': 'MUTABLE', 'type': 'LINK', 'data': {'url': "javascript:alert('oh no')"}}
    +            },
    +            'blocks': [
    +                {
    +                    'inlineStyleRanges': [], 'text': 'an external link', 'depth': 0, 'type': 'unstyled', 'key': '00000',
    +                    'entityRanges': [{'offset': 3, 'length': 8, 'key': 0}]
    +                },
    +            ]
    +        })
    +
    +        result = converter.to_database_format(contentstate_json)
    +        self.assertEqual(result, '<p>an <a>external</a> link</p>')
    
915f6ed2bd7d

Disallow links with unrecognised protocols in contentstate

https://github.com/wagtail/wagtailMatt WestcottApr 8, 2021via ghsa
2 files changed · +55 1
  • wagtail/admin/rich_text/converters/contentstate.py+2 1 modified
    @@ -8,6 +8,7 @@
     
     from wagtail.admin.rich_text.converters.html_to_contentstate import HtmlToContentStateHandler
     from wagtail.core.rich_text import features as feature_registry
    +from wagtail.core.whitelist import check_url
     
     
     def link_entity(props):
    @@ -21,7 +22,7 @@ def link_entity(props):
             link_props['linktype'] = 'page'
             link_props['id'] = id_
         else:
    -        link_props['href'] = props.get('url')
    +        link_props['href'] = check_url(props.get('url'))
     
         return DOM.create_element('a', link_props, props['children'])
     
    
  • wagtail/admin/tests/test_contentstate.py+53 0 modified
    @@ -825,3 +825,56 @@ def test_p_with_class(self):
                 ],
                 'entityMap': {}
             })
    +
    +
    +class TestContentStateToHtml(TestCase):
    +    def test_external_link(self):
    +        converter = ContentstateConverter(features=['link'])
    +        contentstate_json = json.dumps({
    +            'entityMap': {
    +                '0': {'mutability': 'MUTABLE', 'type': 'LINK', 'data': {'url': 'http://wagtail.io'}}
    +            },
    +            'blocks': [
    +                {
    +                    'inlineStyleRanges': [], 'text': 'an external link', 'depth': 0, 'type': 'unstyled', 'key': '00000',
    +                    'entityRanges': [{'offset': 3, 'length': 8, 'key': 0}]
    +                },
    +            ]
    +        })
    +
    +        result = converter.to_database_format(contentstate_json)
    +        self.assertEqual(result, '<p>an <a href="http://wagtail.io">external</a> link</p>')
    +
    +    def test_local_link(self):
    +        converter = ContentstateConverter(features=['link'])
    +        contentstate_json = json.dumps({
    +            'entityMap': {
    +                '0': {'mutability': 'MUTABLE', 'type': 'LINK', 'data': {'url': '/some/local/path/'}}
    +            },
    +            'blocks': [
    +                {
    +                    'inlineStyleRanges': [], 'text': 'an external link', 'depth': 0, 'type': 'unstyled', 'key': '00000',
    +                    'entityRanges': [{'offset': 3, 'length': 8, 'key': 0}]
    +                },
    +            ]
    +        })
    +
    +        result = converter.to_database_format(contentstate_json)
    +        self.assertEqual(result, '<p>an <a href="/some/local/path/">external</a> link</p>')
    +
    +    def test_reject_javascript_link(self):
    +        converter = ContentstateConverter(features=['link'])
    +        contentstate_json = json.dumps({
    +            'entityMap': {
    +                '0': {'mutability': 'MUTABLE', 'type': 'LINK', 'data': {'url': "javascript:alert('oh no')"}}
    +            },
    +            'blocks': [
    +                {
    +                    'inlineStyleRanges': [], 'text': 'an external link', 'depth': 0, 'type': 'unstyled', 'key': '00000',
    +                    'entityRanges': [{'offset': 3, 'length': 8, 'key': 0}]
    +                },
    +            ]
    +        })
    +
    +        result = converter.to_database_format(contentstate_json)
    +        self.assertEqual(result, '<p>an <a>external</a> link</p>')
    

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

9

News mentions

0

No linked articles in our index yet.