diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a3f177315b16c99ece09428b31306ba88161287..6635070320864c5ddf20aeb41f28e929253c8682 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # Changelog +## 2.3.3 +This release fixes a security vulnerability caused by the CORS implementation. +Origins allowed for CORS can now be set in the configuration via +`security.cors.origins`. (Reported by Rainer Rillke at Wikimedia) + +Additional changes: +* Libraries have been upgraded to fix potential bugs + +## 2.2.2 +This release fixes a security vulnerability caused by the CORS implementation. +Origins allowed for CORS can now be set in the configuration via +`security.cors.origins`. (Reported by Rainer Rillke at Wikimedia) + +Additional changes: +* Libraries have been upgraded to fix potential bugs + +## 2.1.2 +This release fixes a security vulnerability caused by the CORS implementation. +Support for cross-origin requests has been removed. Use ARSnova version 2.2 or +newer for proper CORS. (Reported by Rainer Rillke at Wikimedia) + +Additional changes: +* Libraries have been upgraded to fix potential bugs + +## 2.0.4 +This release fixes a security vulnerability caused by the CORS implementation. +Support for cross-origin requests has been removed. Use ARSnova version 2.2 or +newer for proper CORS. (Reported by Rainer Rillke at Wikimedia) + +Additional changes: +* Libraries have been upgraded to fix potential bugs + ## 2.4 Major features: * Support for new use case and feature settings has been added. diff --git a/pom.xml b/pom.xml index 36254d827930a653eef9dc7029a83959dcf62a5f..390523a5f21dd96e6c783f29ec68c9815edf5bbb 100644 --- a/pom.xml +++ b/pom.xml @@ -80,7 +80,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> - <version>2.10.3</version> + <version>2.10.4</version> <configuration></configuration> </plugin> </plugins> @@ -177,7 +177,7 @@ <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> - <version>3.2.2</version> + <version>3.2.4</version> </dependency> <dependency> <groupId>org.slf4j</groupId> @@ -325,7 +325,7 @@ <plugin> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> - <version>9.2.16.v20160414</version> + <version>9.2.17.v20160517</version> <configuration> <scanIntervalSeconds>1</scanIntervalSeconds> <webApp> @@ -365,7 +365,7 @@ <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> - <version>0.7.6.201602180812</version> + <version>0.7.7.201606060606</version> <executions> <execution> <id>default-prepare-agent</id> diff --git a/src/main/java/de/thm/arsnova/config/ExtraConfig.java b/src/main/java/de/thm/arsnova/config/ExtraConfig.java index ff713d4e95c408fe4c36882c9ca7b11fc363146b..e37d28ed49a21e562cda22c90a96f6776a0cf44a 100644 --- a/src/main/java/de/thm/arsnova/config/ExtraConfig.java +++ b/src/main/java/de/thm/arsnova/config/ExtraConfig.java @@ -18,6 +18,7 @@ package de.thm.arsnova.config; import de.thm.arsnova.ImageUtils; +import de.thm.arsnova.web.CorsFilter; import de.thm.arsnova.connector.client.ConnectorClient; import de.thm.arsnova.connector.client.ConnectorClientImpl; import de.thm.arsnova.socket.ARSnovaSocket; @@ -40,6 +41,9 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import java.util.Arrays; +import java.util.Collections; + /** * Loads property file and configures non-security related beans and components. */ @@ -61,6 +65,7 @@ public class ExtraConfig extends WebMvcConfigurerAdapter { @Value(value = "${security.ssl}") private boolean socketUseSll; @Value(value = "${security.keystore}") private String socketKeystore; @Value(value = "${security.storepass}") private String socketStorepass; + @Value(value = "${security.cors.origins:}") private String[] corsOrigins; private static int testPortOffset = 0; @@ -84,6 +89,11 @@ public class ExtraConfig extends WebMvcConfigurerAdapter { return propertiesFactoryBean; } + @Bean + public CorsFilter corsFilter() { + return new CorsFilter(Arrays.asList(corsOrigins)); + } + @Bean(name = "connectorClient") public ConnectorClient connectorClient() { if (!connectorEnable) { diff --git a/src/main/java/de/thm/arsnova/web/CorsFilter.java b/src/main/java/de/thm/arsnova/web/CorsFilter.java index f4c434e02cc65f0a89da48cdb9acda407780cdd1..acd2f7dc1541bbee46c504e0a90d2a5d9065e3de 100644 --- a/src/main/java/de/thm/arsnova/web/CorsFilter.java +++ b/src/main/java/de/thm/arsnova/web/CorsFilter.java @@ -17,36 +17,52 @@ */ package de.thm.arsnova.web; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; +import java.util.List; -/** - * Sets response headers to allow CORS requests. - */ -@Component -public class CorsFilter extends OncePerRequestFilter { +public class CorsFilter extends org.springframework.web.filter.CorsFilter { + protected final Logger logger = LoggerFactory.getLogger(CorsFilter.class); + + public CorsFilter(List<String> origins) { + super(configurationSource(origins)); + logger.info("CorsFilter initialized. Allowed origins: {}", origins); + } - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - response.addHeader("Access-Control-Allow-Credentials", "true"); - response.addHeader("Access-Control-Allow-Methods", "GET"); - response.addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With"); + private static UrlBasedCorsConfigurationSource configurationSource(List<String> origins) { + CorsConfiguration config; + UrlBasedCorsConfigurationSource source; - if (request.getHeader("origin") != null) { - response.addHeader("Access-Control-Allow-Origin", sanitizeOriginUrl(request.getHeader("origin"))); - } + /* Grant full access from specified origins */ + config = new CorsConfiguration(); + config.setAllowedOrigins(origins); + config.addAllowedHeader("Accept"); + config.addAllowedHeader("Content-Type"); + config.addAllowedHeader("X-Requested-With"); + config.addAllowedMethod("GET"); + config.addAllowedMethod("POST"); + config.addAllowedMethod("PUT"); + config.addAllowedMethod("DELETE"); + config.setAllowCredentials(true); + source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); - filterChain.doFilter(request, response); - } + /* Grant limited access from all origins */ + config = new CorsConfiguration(); + config.addAllowedOrigin("*"); + config.addAllowedHeader("Accept"); + config.addAllowedHeader("X-Requested-With"); + config.addAllowedMethod("GET"); + config.setAllowCredentials(true); + source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/", config); + source.registerCorsConfiguration("/arsnova-config", config); + source.registerCorsConfiguration("/configuration/", config); + source.registerCorsConfiguration("/statistics", config); - private String sanitizeOriginUrl(String originUrl) { - return originUrl.replaceAll("[\n\r]+", " "); + return source; } } diff --git a/src/main/resources/arsnova.properties.example b/src/main/resources/arsnova.properties.example index f7efb747218faba8df18a1964abd541f243b72fe..ee1bab9736f07a5332cb7ac7d48864792cc3d992 100644 --- a/src/main/resources/arsnova.properties.example +++ b/src/main/resources/arsnova.properties.example @@ -167,6 +167,15 @@ security.google.key= security.google.secret= +################################################################################ +# Cross-Origin Resource Sharing +################################################################################ +# CORS grants full API access to client-side (browser) applications from other +# domains. Multiple entries are separated by commas. Untrusted and vulnerable +# applications running on these domains pose a security risk to ARSnova users. +#security.cors.origins=https:// + + ################################################################################ # ARSnova Connector (for LMS) ################################################################################ diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 7daa0e4c21695641e954b8e1b98c8239c3c9e33a..95f404bd0d1c9ec724d76b00eb5aba0bff6f4818 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -63,7 +63,7 @@ <filter> <filter-name>corsFilter</filter-name> - <filter-class>de.thm.arsnova.web.CorsFilter</filter-class> + <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <async-supported>true</async-supported> </filter> <filter-mapping> diff --git a/src/test/resources/arsnova.properties.example b/src/test/resources/arsnova.properties.example index 3f118a73331349cb3c25362bf29e8e5db0dd5e71..b9bd4b7891dc305f7f721c2067a677f9b793392a 100644 --- a/src/test/resources/arsnova.properties.example +++ b/src/test/resources/arsnova.properties.example @@ -160,6 +160,15 @@ security.google.key= security.google.secret= +################################################################################ +# Cross-Origin Resource Sharing +################################################################################ +# CORS grants full API access to client-side (browser) applications from other +# domains. Multiple entries are separated by commas. Untrusted and vulnerable +# applications running on these domains pose a security risk to ARSnova users. +#security.cors.origins=https:// + + ################################################################################ # ARSnova Connector (for LMS) ################################################################################