From 277983982024a703888152be3a7b4d6e5bc2fc14 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Wed, 16 Oct 2024 15:56:06 +0200 Subject: [PATCH] SONAR-23205 Genereate CSP hash of assetsPath script based WEB_CONTEXT value --- .../sonar/server/platform/web/CspFilter.java | 28 ++++++++++++++++++- .../server/platform/web/CspFilterTest.java | 17 +++++++++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/CspFilter.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/CspFilter.java index a0f7b100dbc..2a6372101d9 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/CspFilter.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/CspFilter.java @@ -20,7 +20,11 @@ package org.sonar.server.platform.web; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Base64; import java.util.List; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -47,7 +51,7 @@ public class CspFilter implements Filter { cspPolicies.add("img-src * data: blob:"); cspPolicies.add("object-src 'none'"); // the hash below corresponds to the window.__assetsPath script in index.html - cspPolicies.add("script-src 'self' 'sha256-D1jaqcDDM2TM2STrzE42NNqyKR9PlptcHDe6tyaBcuM='"); + cspPolicies.add("script-src 'self' " + getAssetsPathScriptCSPHash(filterConfig.getServletContext().getContextPath())); cspPolicies.add("style-src 'self' 'unsafe-inline'"); cspPolicies.add("worker-src 'none'"); this.policies = String.join("; ", cspPolicies).trim(); @@ -68,4 +72,26 @@ public class CspFilter implements Filter { // Not used } + private static String getAssetsPathScriptCSPHash(String contextPath) { + final String WEB_CONTEXT_PLACEHOLDER = "WEB_CONTEXT"; + final String ASSETS_PATH_SCRIPT = "\n" + + " window.__assetsPath = function (filename) {\n" + + " return 'WEB_CONTEXT/' + filename;\n" + + " };\n" + + " "; + + String assetsPathScriptWithContextPath = ASSETS_PATH_SCRIPT.replace(WEB_CONTEXT_PLACEHOLDER, contextPath); + return generateCSPHash(assetsPathScriptWithContextPath); + } + + private static String generateCSPHash(String str) { + try { + byte[] bytes = str.getBytes(StandardCharsets.UTF_8); + byte[] digestBytes = MessageDigest.getInstance("SHA-256").digest(bytes); + String rawHash = Base64.getMimeEncoder().encodeToString(digestBytes); + return String.format("'%s-%s'", "sha256", rawHash); + } catch (NoSuchAlgorithmException e) { + return ""; + } + } } diff --git a/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/CspFilterTest.java b/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/CspFilterTest.java index 77290ec96d5..5cf592360a4 100644 --- a/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/CspFilterTest.java +++ b/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/CspFilterTest.java @@ -28,6 +28,8 @@ import javax.servlet.http.HttpServletResponse; import org.junit.Before; import org.junit.Test; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.RETURNS_MOCKS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -42,7 +44,7 @@ public class CspFilterTest { "font-src 'self' data:; " + "img-src * data: blob:; " + "object-src 'none'; " + - "script-src 'self' 'sha256-D1jaqcDDM2TM2STrzE42NNqyKR9PlptcHDe6tyaBcuM='; " + + "script-src 'self' 'sha256-hK8SVWFNHY0UhP61DBzX/3fvT74EI8u6/jRQvUKeZoU='; " + "style-src 'self' 'unsafe-inline'; " + "worker-src 'none'"; private final ServletContext servletContext = mock(ServletContext.class, RETURNS_MOCKS); @@ -53,11 +55,12 @@ public class CspFilterTest { @Before public void setUp() throws ServletException { - when(servletContext.getContextPath()).thenReturn(TEST_CONTEXT); + when(config.getServletContext()).thenReturn(servletContext); } @Test public void set_content_security_headers() throws Exception { + when(servletContext.getContextPath()).thenReturn(TEST_CONTEXT); doInit(); HttpServletRequest request = newRequest("/"); underTest.doFilter(request, response, chain); @@ -65,6 +68,16 @@ public class CspFilterTest { verify(chain).doFilter(request, response); } + @Test + public void csp_hash_should_be_correct_without_a_context_path() throws Exception { + when(servletContext.getContextPath()).thenReturn(""); + doInit(); + HttpServletRequest request = newRequest("/"); + underTest.doFilter(request, response, chain); + verify(response).setHeader(eq("Content-Security-Policy"), contains("script-src 'self' 'sha256-D1jaqcDDM2TM2STrzE42NNqyKR9PlptcHDe6tyaBcuM='; ")); + verify(chain).doFilter(request, response); + } + private void doInit() throws ServletException { underTest.init(config); } -- 2.39.5