From 78237483c658e35f35bf5044e2c4aa0b8993ba2a Mon Sep 17 00:00:00 2001 From: alain <108417558+alain-kermis-sonarsource@users.noreply.github.com> Date: Tue, 29 Nov 2022 14:12:45 +0100 Subject: [PATCH] SONAR-17619 Fix SSF-157 --- server/sonar-web/public/WEB-INF/web.xml | 9 +++ .../sonar/server/platform/web/CspFilter.java | 71 ++++++++++++++++ .../server/platform/web/CspFilterTest.java | 81 +++++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 server/sonar-webserver/src/main/java/org/sonar/server/platform/web/CspFilter.java create mode 100644 server/sonar-webserver/src/test/java/org/sonar/server/platform/web/CspFilterTest.java diff --git a/server/sonar-web/public/WEB-INF/web.xml b/server/sonar-web/public/WEB-INF/web.xml index da4795bfbef..6ee3d23b5fa 100644 --- a/server/sonar-web/public/WEB-INF/web.xml +++ b/server/sonar-web/public/WEB-INF/web.xml @@ -57,6 +57,11 @@ org.sonar.server.platform.web.CacheControlFilter true + + CspFilter + org.sonar.server.platform.web.CspFilter + true + @@ -83,6 +88,10 @@ CacheControlFilter /* + + CspFilter + /* + UserSessionFilter /* 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 new file mode 100644 index 00000000000..2cdd606e5c4 --- /dev/null +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/CspFilter.java @@ -0,0 +1,71 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform.web; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; + +public class CspFilter implements Filter { + + private final List cspHeaders = new ArrayList<>(); + private String policies = null; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + cspHeaders.add("Content-Security-Policy"); + cspHeaders.add("X-Content-Security-Policy"); + cspHeaders.add("X-WebKit-CSP"); + + List cspPolicies = new ArrayList<>(); + cspPolicies.add("default-src 'self'"); + cspPolicies.add("base-uri 'none'"); + cspPolicies.add("connect-src 'self' http: https:"); + cspPolicies.add("img-src * data: blob:"); + cspPolicies.add("object-src 'none'"); + cspPolicies.add("script-src 'self' 'unsafe-inline' 'unsafe-eval'"); + cspPolicies.add("style-src 'self' 'unsafe-inline'"); + cspPolicies.add("worker-src 'none'"); + this.policies = String.join("; ", cspPolicies).trim(); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + // Add policies to all HTTP headers + for (String header : this.cspHeaders) { + ((HttpServletResponse) response).setHeader(header, this.policies); + } + + chain.doFilter(request, response); + } + + @Override + public void destroy() { + // Not used + } + +} 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 new file mode 100644 index 00000000000..c73365dae8e --- /dev/null +++ b/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/CspFilterTest.java @@ -0,0 +1,81 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform.web; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.Before; +import org.junit.Test; + +import static org.mockito.Mockito.RETURNS_MOCKS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class CspFilterTest { + + private static final String TEST_CONTEXT = "/sonarqube"; + private static final String EXPECTED = "default-src 'self'; " + + "base-uri 'none'; " + + "connect-src 'self' http: https:; " + + "img-src * data: blob:; " + + "object-src 'none'; " + + "script-src 'self' 'unsafe-inline' 'unsafe-eval'; " + + "style-src 'self' 'unsafe-inline'; " + + "worker-src 'none'"; + private final ServletContext servletContext = mock(ServletContext.class, RETURNS_MOCKS); + private final HttpServletResponse response = mock(HttpServletResponse.class); + private final FilterChain chain = mock(FilterChain.class); + private final CspFilter underTest = new CspFilter(); + FilterConfig config = mock(FilterConfig.class); + + @Before + public void setUp() throws ServletException { + when(servletContext.getContextPath()).thenReturn(TEST_CONTEXT); + } + + @Test + public void set_content_security_headers() throws Exception { + doInit(); + HttpServletRequest request = newRequest("/"); + underTest.doFilter(request, response, chain); + verify(response).setHeader("Content-Security-Policy", EXPECTED); + verify(response).setHeader("X-Content-Security-Policy", EXPECTED); + verify(response).setHeader("X-WebKit-CSP", EXPECTED); + verify(chain).doFilter(request, response); + } + + private void doInit() throws ServletException { + underTest.init(config); + } + + private HttpServletRequest newRequest(String path) { + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getMethod()).thenReturn("GET"); + when(req.getRequestURI()).thenReturn(path); + when(req.getContextPath()).thenReturn(""); + when(req.getServletContext()).thenReturn(this.servletContext); + return req; + } +} -- 2.39.5