Просмотр исходного кода

#22675 Validate RSS feed entry link schemes to prevent javascript: XSS

Arthur 1 неделя назад
Родитель
Сommit
31301cdb95
1 измененных файлов с 64 добавлено и 1 удалено
  1. 64 1
      netbox/extras/tests/test_dashboard.py

+ 64 - 1
netbox/extras/tests/test_dashboard.py

@@ -1,6 +1,7 @@
+from django.core.cache import cache
 from django.test import RequestFactory, TestCase, tag
 
-from extras.dashboard.widgets import ObjectListWidget
+from extras.dashboard.widgets import ObjectListWidget, RSSFeedWidget
 from extras.templatetags.dashboard import render_widget
 
 
@@ -49,6 +50,68 @@ class ObjectListWidgetTestCase(TestCase):
         self.assertTrue('Unable to load content. Could not resolve list URL for:' in rendered)
 
 
+class RSSFeedWidgetSanitizationTestCase(TestCase):
+    """
+    Feed entry content is externally controlled and untrusted. Links must be validated against
+    ALLOWED_URL_SCHEMES so dangerous schemes (e.g. javascript:) cannot become clickable XSS sinks.
+    """
+
+    @tag('regression')
+    def test_sanitize_entries_blanks_disallowed_schemes(self):
+        entries = [
+            {'link': 'javascript:alert(document.cookie)', 'title': 't1'},
+            {'link': 'JavaScript:alert(1)', 'title': 't2'},  # case-insensitive
+            {'link': 'data:text/html,<script>alert(1)</script>', 'title': 't3'},
+            {'link': 'vbscript:msgbox(1)', 'title': 't4'},
+        ]
+        RSSFeedWidget.sanitize_entries(entries)
+        for entry in entries:
+            self.assertEqual(entry['link'], '', msg=f"Failed to blank {entry['title']}")
+
+    @tag('regression')
+    def test_sanitize_entries_preserves_allowed_links(self):
+        entries = [
+            {'link': 'https://example.com/post', 'title': 't1'},
+            {'link': 'http://example.com/post', 'title': 't2'},
+            {'link': 'mailto:user@example.com', 'title': 't3'},
+            {'link': '/relative/path', 'title': 't4'},  # schemeless relative link
+        ]
+        expected = [e['link'] for e in entries]
+        RSSFeedWidget.sanitize_entries(entries)
+        self.assertEqual([e['link'] for e in entries], expected)
+
+    @tag('regression')
+    def test_sanitize_entries_cleans_summary_html(self):
+        entries = [
+            {'link': 'https://example.com', 'title': 't1', 'summary': '<b>ok</b><script>alert(1)</script>'},
+        ]
+        RSSFeedWidget.sanitize_entries(entries)
+        self.assertNotIn('<script>', entries[0]['summary'])
+        self.assertIn('<b>ok</b>', entries[0]['summary'])
+
+    @tag('regression')
+    def test_get_feed_sanitizes_cached_content(self):
+        """
+        Content cached by a pre-fix release must be sanitized on read, not served verbatim.
+        """
+        widget = RSSFeedWidget(config={
+            'feed_url': 'https://example.com/feed.xml',
+            'requires_internet': False,
+            'max_entries': 10,
+        })
+        # Simulate a poisoned feed left in the cache by an older release
+        cache.set(widget.cache_key, {
+            'bozo': False,
+            'entries': [
+                {'link': 'javascript:alert(1)', 'title': 'evil', 'summary': 'x'},
+            ],
+        })
+
+        result = widget.get_feed()
+
+        self.assertEqual(result['feed']['entries'][0]['link'], '')
+
+
 class RenderWidgetTemplateTagTestCase(TestCase):
 
     def _make_context(self):