Opened 60 minutes ago

Last modified 54 minutes ago

#37419 assigned Bug

Include CSP nonces in headers for streaming responses

Reported by: Sarah Boyce Owned by: Sarah Boyce
Component: Core (Other) Version: 6.1
Severity: Normal Keywords:
Cc: Rob Hudson, Jake Howard Triage Stage: Unreviewed
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Background:
The security team received a report from Michael Codispoti (codispoti) on HackerOne. This report was agreed to be handled in public as a hardening as the CSP middleware doesn't absolve developer responsibility to escape untrusted input.

Issue:
For a StreamingHttpResponse, the response headers are constructed before the streaming iterator is consumed.
If the iterator first accesses csp_nonce while producing the response body, the HTML receives a generated nonce after the CSP header has already been finalized.

This causes the streamed HTML to contain a nonce that is absent from the corresponding Content-Security-Policy header.

Regression test:

  • tests/middleware/test_csp.py

    diff --git a/tests/middleware/test_csp.py b/tests/middleware/test_csp.py
    index f0a4af2a7c..c64754c221 100644
    a b from playwright_tests import PlaywrightTestCase  
    22from utils_tests.test_csp import basic_config, basic_policy
    33
    44from django.contrib.staticfiles.testing import StaticLiveServerTestCase
    5 from django.test import SimpleTestCase
     5from django.http import StreamingHttpResponse
     6from django.middleware.csp import ContentSecurityPolicyMiddleware
     7from django.template import Context, Template
     8from django.template.context_processors import csp
     9from django.test import RequestFactory, SimpleTestCase
    610from django.test.utils import modify_settings, override_settings
    711from django.utils.csp import CSP
    812
    … … class CSPMiddlewareWithDecoratedViewsTest(SimpleTestCase):  
    173177        )
    174178
    175179
     180class CSPStreamingTests(SimpleTestCase):
     181    @override_settings(
     182        SECURE_CSP={
     183            "default-src": [CSP.SELF],
     184            "script-src": [CSP.NONCE],
     185        },
     186        SECURE_CSP_REPORT_ONLY={},
     187    )
     188    def test_nonce_generated_during_streaming_is_in_header(self):
     189        request = RequestFactory().get("/")
     190        template = Template('<script nonce="{{ csp_nonce }}"></script>')
     191
     192        def view(request):
     193            def content():
     194                yield template.render(Context(csp(request)))
     195
     196            return StreamingHttpResponse(content())
     197
     198        response = ContentSecurityPolicyMiddleware(view)(request)
     199        body = b"".join(response.streaming_content).decode()
     200
     201        nonce = body.split('nonce="', 1)[1].split('"', 1)[0]
     202        self.assertTrue(nonce)
     203        self.assertIn(f"'nonce-{nonce}'", response["Content-Security-Policy"])
     204
     205
    176206@override_settings(
    177207    ROOT_URLCONF="middleware.urls",

Change History (2)

comment:1 by Sarah Boyce, 55 minutes ago

Has patch: set

comment:2 by Sarah Boyce, 54 minutes ago

Cc: Rob Hudson Jake Howard added
Note: See TracTickets for help on using tickets.
Back to Top