Opened 3 weeks ago
Closed 11 days ago
#37419 closed Bug (fixed)
Include CSP nonces in headers for streaming responses
| Reported by: | Sarah Boyce | Owned by: | Sarah Boyce |
|---|---|---|---|
| Component: | CSP | Version: | 6.1 |
| Severity: | Normal | Keywords: | |
| Cc: | Rob Hudson, Jake Howard | Triage Stage: | Ready for checkin |
| 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 (11)
comment:1 by , 3 weeks ago
| Has patch: | set |
|---|
comment:2 by , 3 weeks ago
| Cc: | added |
|---|
comment:3 by , 3 weeks ago
| Patch needs improvement: | set |
|---|
comment:4 by , 3 weeks ago
| Patch needs improvement: | unset |
|---|
comment:5 by , 3 weeks ago
| Triage Stage: | Unreviewed → Accepted |
|---|
comment:6 by , 3 weeks ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
comment:7 by , 3 weeks ago
| Triage Stage: | Ready for checkin → Accepted |
|---|
comment:8 by , 12 days ago
| Component: | Core (Other) → CSP |
|---|
comment:9 by , 12 days ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
I guess we're taking away the ability of streaming responses to *not* have a nonce applied, but I suppose Django should solve the 80% use case and leave that detail to be worked out with custom middleware if needed.