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 2 2 from utils_tests.test_csp import basic_config, basic_policy 3 3 4 4 from django.contrib.staticfiles.testing import StaticLiveServerTestCase 5 from django.test import SimpleTestCase 5 from django.http import StreamingHttpResponse 6 from django.middleware.csp import ContentSecurityPolicyMiddleware 7 from django.template import Context, Template 8 from django.template.context_processors import csp 9 from django.test import RequestFactory, SimpleTestCase 6 10 from django.test.utils import modify_settings, override_settings 7 11 from django.utils.csp import CSP 8 12 … … class CSPMiddlewareWithDecoratedViewsTest(SimpleTestCase): 173 177 ) 174 178 175 179 180 class 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 176 206 @override_settings( 177 207 ROOT_URLCONF="middleware.urls",
Change History (2)
comment:1 by , 55 minutes ago
| Has patch: | set |
|---|
comment:2 by , 54 minutes ago
| Cc: | added |
|---|