From 00ba232bbff44de427e6b33fb4b7147002c96c08 Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Thu, 1 Oct 2015 13:53:00 +0200 Subject: [PATCH] SONAR-6881 backport SecurityServletFilter to 4.5 --- .../platform/SecurityServletFilter.java | 78 +++++++++++++ .../platform/SecurityServletFilterTest.java | 103 ++++++++++++++++++ .../main/webapp/WEB-INF/config/environment.rb | 32 ++++++ .../sonar-web/src/main/webapp/WEB-INF/web.xml | 8 ++ 4 files changed, 221 insertions(+) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/platform/SecurityServletFilter.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/platform/SecurityServletFilterTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/SecurityServletFilter.java b/server/sonar-server/src/main/java/org/sonar/server/platform/SecurityServletFilter.java new file mode 100644 index 00000000000..98d0848e0d0 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/SecurityServletFilter.java @@ -0,0 +1,78 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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; + +import com.google.common.collect.ImmutableSet; +import java.io.IOException; +import java.util.Set; +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.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * This servlet filter sets response headers that enable browser protection against several classes if Web attacks. + * The list of headers is mirrored in environment.rb as a workaround to Rack swallowing the headers.. + */ +public class SecurityServletFilter implements Filter { + + private static final Set ALLOWED_HTTP_METHODS = ImmutableSet.of("DELETE", "GET", "HEAD", "POST", "PUT"); + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // nothing + } + + @Override + public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { + doHttpFilter((HttpServletRequest) req, (HttpServletResponse) resp, chain); + } + + private static void doHttpFilter(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain chain) throws IOException, ServletException { + // SONAR-6881 Disable OPTIONS and TRACE methods + if (!ALLOWED_HTTP_METHODS.contains(httpRequest.getMethod())) { + httpResponse.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + return; + } + + chain.doFilter(httpRequest, httpResponse); + + // Clickjacking protection + // See https://www.owasp.org/index.php/Clickjacking_Protection_for_Java_EE + httpResponse.addHeader("X-Frame-Options", "SAMEORIGIN"); + + // Cross-site scripting + // See https://www.owasp.org/index.php/List_of_useful_HTTP_headers + httpResponse.addHeader("X-XSS-Protection", "1; mode=block"); + + // MIME-sniffing + // See https://www.owasp.org/index.php/List_of_useful_HTTP_headers + httpResponse.addHeader("X-Content-Type-Options", "nosniff"); + } + + @Override + public void destroy() { + // nothing + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/SecurityServletFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/SecurityServletFilterTest.java new file mode 100644 index 00000000000..256a8064429 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/SecurityServletFilterTest.java @@ -0,0 +1,103 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.Test; + +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.startsWith; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class SecurityServletFilterTest { + + SecurityServletFilter underTest = new SecurityServletFilter(); + HttpServletResponse response = mock(HttpServletResponse.class); + FilterChain chain = mock(FilterChain.class); + + @Test + public void allow_GET_method() throws IOException, ServletException { + assertThatMethodIsAllowed("GET"); + } + + @Test + public void allow_HEAD_method() throws IOException, ServletException { + assertThatMethodIsAllowed("HEAD"); + } + + @Test + public void allow_PUT_method() throws IOException, ServletException { + assertThatMethodIsAllowed("PUT"); + } + + @Test + public void allow_POST_method() throws IOException, ServletException { + assertThatMethodIsAllowed("POST"); + } + + private void assertThatMethodIsAllowed(String httpMethod) throws IOException, ServletException { + HttpServletRequest request = newRequest(httpMethod); + underTest.doFilter(request, response, chain); + verify(response, never()).setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + verify(chain).doFilter(request, response); + } + + @Test + public void deny_OPTIONS_method() throws IOException, ServletException { + assertThatMethodIsDenied("OPTIONS"); + } + + @Test + public void deny_TRACE_method() throws IOException, ServletException { + assertThatMethodIsDenied("TRACE"); + } + + private void assertThatMethodIsDenied(String httpMethod) throws IOException, ServletException { + underTest.doFilter(newRequest(httpMethod), response, chain); + verify(response).setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + } + + @Test + public void set_secured_headers() throws ServletException, IOException { + underTest.init(mock(FilterConfig.class)); + HttpServletRequest request = newRequest("GET"); + + underTest.doFilter(request, response, chain); + + verify(response, times(3)).addHeader(startsWith("X-"), anyString()); + + underTest.destroy(); + } + + private HttpServletRequest newRequest(String httpMethod) { + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getMethod()).thenReturn(httpMethod); + return req; + } +} diff --git a/server/sonar-web/src/main/webapp/WEB-INF/config/environment.rb b/server/sonar-web/src/main/webapp/WEB-INF/config/environment.rb index cbe9edcf315..b93dc9152dc 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/config/environment.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/config/environment.rb @@ -52,6 +52,35 @@ class EagerPluginLoader < Rails::Plugin::Loader end end +# +# Put response headers on all HTTP calls. This is done by the Java SecurityServlerFilter, +# but for some reason Rack swallows the headers set on Java side. +# See middleware configuration below. +# +class SecurityHeaders + def initialize(app) + @app = app + end + + def call(env) + status, headers, body = @app.call(env) + + # Clickjacking protection + # See https://www.owasp.org/index.php/Clickjacking_Protection_for_Java_EE + headers['X-Frame-Options']='SAMEORIGIN' + + # Cross-site scripting + # See https://www.owasp.org/index.php/List_of_useful_HTTP_headers + headers['X-XSS-Protection']='1; mode=block' + + # MIME-sniffing + # See https://www.owasp.org/index.php/List_of_useful_HTTP_headers + headers['X-Content-Type-Options']='nosniff'; + + [status, headers, body] + end +end + Rails::Initializer.run do |config| # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers @@ -108,6 +137,9 @@ Rails::Initializer.run do |config| # Activate observers that should always be running # Please note that observers generated using script/generate observer need to have an _observer suffix # config.active_record.observers = :cacher, :garbage_collector, :forum_observer + + # Add security related headers + config.middleware.use SecurityHeaders end diff --git a/server/sonar-web/src/main/webapp/WEB-INF/web.xml b/server/sonar-web/src/main/webapp/WEB-INF/web.xml index b4896f15c56..bb2cc8d743b 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/web.xml +++ b/server/sonar-web/src/main/webapp/WEB-INF/web.xml @@ -47,6 +47,10 @@ RackFilter org.jruby.rack.RackFilter + + SecurityFilter + org.sonar.server.platform.SecurityServletFilter + ProfilingFilter org.sonar.server.platform.ProfilingFilter @@ -73,6 +77,10 @@ ServletFilters /* + + SecurityFilter + /* + RackFilter /* -- 2.39.5