Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanseifert committed Apr 25, 2024
2 parents 315dd58 + c809c42 commit 9e1fe9c
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 12 deletions.
7 changes: 7 additions & 0 deletions changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@
xsi:schemaLocation="http://maven.apache.org/changes/1.0.0 http://maven.apache.org/plugins/maven-changes-plugin/xsd/changes-1.0.0.xsd">
<body>

<release version="2.0.6" date="2024-04-25">
<action type="fix" dev="sseifert" issue="50">
MediaFileServlet: Use Content-Security-Policy instead of Content-Disposition header to prevent XSS attacks in SVG files.
Make this behavior configurable via OSGi configuration, as it may prevent special use cases e.g. SVGs animatd via JavaScript.
</action>
</release>

<release version="2.0.4" date="2024-04-17">
<action type="fix" dev="sseifert" issue="49">
Fix failing to resolve media when enforceVirtualRenditions is enabled and auto cropping is used at the same time.
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

<groupId>io.wcm</groupId>
<artifactId>io.wcm.handler.media</artifactId>
<version>2.0.4</version>
<version>2.0.6</version>
<packaging>jar</packaging>

<name>Media Handler</name>
Expand All @@ -49,7 +49,7 @@
<site.url.module.prefix>handler/media</site.url.module.prefix>

<!-- Enable reproducible builds -->
<project.build.outputTimestamp>2024-04-17T07:52:06Z</project.build.outputTimestamp>
<project.build.outputTimestamp>2024-04-25T15:40:14Z</project.build.outputTimestamp>
</properties>

<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package io.wcm.handler.media.impl;

import static io.wcm.handler.media.impl.MediaFileServletConstants.HEADER_CONTENT_DISPOSITION;
import static io.wcm.handler.media.impl.MediaFileServletConstants.HEADER_CONTENT_SECURITY_POLICY;
import static io.wcm.handler.media.impl.MediaFileServletConstants.SELECTOR_DOWNLOAD;

import java.io.IOException;
Expand Down Expand Up @@ -159,9 +160,9 @@ protected void sendBinaryData(byte @NotNull [] binaryData, @NotNull String conte
}

// special handling for SVG images which are not treated as download:
// force content disposition header to prevent stored XSS attack with malicious JavaScript in SVG file
else if (StringUtils.equals(contentType, ContentType.SVG)) {
setContentDispositionAttachmentHeader(request, response);
// set content security policy to prevent stored XSS attack with malicious JavaScript in SVG file
if (StringUtils.equals(contentType, ContentType.SVG)) {
setSVGContentSecurityPolicy(response);
}

// write binary data
Expand All @@ -181,4 +182,8 @@ private void setContentDispositionAttachmentHeader(@NotNull SlingHttpServletRequ
response.setHeader(HEADER_CONTENT_DISPOSITION, dispositionHeader.toString());
}

protected void setSVGContentSecurityPolicy(@NotNull SlingHttpServletResponse response) {
response.setHeader(HEADER_CONTENT_SECURITY_POLICY, "sandbox");
}

}
33 changes: 32 additions & 1 deletion src/main/java/io/wcm/handler/media/impl/MediaFileServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,14 @@

import javax.servlet.Servlet;

import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.HttpConstants;
import org.jetbrains.annotations.NotNull;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;

import com.day.cq.commons.jcr.JcrConstants;

Expand All @@ -37,9 +43,34 @@
"sling.servlet.resourceTypes=" + JcrConstants.NT_RESOURCE,
"sling.servlet.methods=" + HttpConstants.METHOD_GET
})
@Designate(ocd = MediaFileServlet.Config.class)
public final class MediaFileServlet extends AbstractMediaFileServlet {
private static final long serialVersionUID = 1L;

// inherits all functionality
private boolean svgContentSecurityPolicy;

@ObjectClassDefinition(
name = "wcm.io Media Handler Media File Servlet",
description = "Configures delivery of media file binaries.")
@interface Config {

@AttributeDefinition(
name = "SVG Content Security Policy",
description = "Apply XSS protection when serving SVG files by setting Content-Security-Policy to 'sandbox'.")
boolean svgContentSecurityPolicy() default true;

}

@Activate
private void activate(Config config) {
this.svgContentSecurityPolicy = config.svgContentSecurityPolicy();
}

@Override
protected void setSVGContentSecurityPolicy(@NotNull SlingHttpServletResponse response) {
if (this.svgContentSecurityPolicy) {
super.setSVGContentSecurityPolicy(response);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ public final class MediaFileServletConstants {
*/
public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";

/**
* Content disposition header
*/
public static final String HEADER_CONTENT_SECURITY_POLICY = "Content-Security-Policy";

/**
* Selector
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
public class WebOptimizedImageDeliveryServiceImpl implements WebOptimizedImageDeliveryService {

@ObjectClassDefinition(
name = "wcm.io Web-Optimized Image Delivery Support",
name = "wcm.io Media Handler Web-Optimized Image Delivery Support",
description = "Support for Next Generation Dynamic Media Web-Optimized Image Delivery capabilites.")
@interface Config {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
public class NextGenDynamicMediaConfigServiceImpl implements NextGenDynamicMediaConfigService {

@ObjectClassDefinition(
name = "wcm.io Next Generation Dynamic Media Support",
name = "wcm.io Media Handler Next Generation Dynamic Media Support",
description = "Support for Next Generation Dynamic Media.")
@interface Config {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
public class NextGenDynamicMediaMetadataServiceImpl implements NextGenDynamicMediaMetadataService {

@ObjectClassDefinition(
name = "wcm.io Next Generation Dynamic Media Metadata Service",
name = "wcm.io Media Handler Next Generation Dynamic Media Metadata Service",
description = "Fetches metadata for Next Generation Dynamic Media assets.")
@interface Config {

Expand Down
25 changes: 22 additions & 3 deletions src/test/java/io/wcm/handler/media/impl/MediaFileServletTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package io.wcm.handler.media.impl;

import static io.wcm.handler.media.impl.MediaFileServletConstants.HEADER_CONTENT_DISPOSITION;
import static io.wcm.handler.media.impl.MediaFileServletConstants.HEADER_CONTENT_SECURITY_POLICY;
import static io.wcm.handler.media.impl.MediaFileServletConstants.SELECTOR_DOWNLOAD;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
Expand Down Expand Up @@ -48,7 +49,7 @@ class MediaFileServletTest {

@BeforeEach
void setUp() {
underTest = new MediaFileServlet();
underTest = context.registerInjectActivateService(MediaFileServlet.class);
context.currentResource(context.load().binaryFile("/sample_image_215x102.jpg", "/content/sample_image.jpg"));
}

Expand All @@ -73,8 +74,26 @@ void testGet_SVG() throws Exception {
assertEquals(ContentType.SVG, context.response().getContentType());
assertEquals(EXPECTED_CONTENT_LENGTH_SVG, context.response().getOutput().length);
assertEquals(EXPECTED_CONTENT_LENGTH_SVG, context.response().getContentLength());
// forced content disposition header for SVG to prevent stored XSS
assertEquals("attachment;", context.response().getHeader(HEADER_CONTENT_DISPOSITION));
// forced content security policy for SVG to prevent stored XSS
assertEquals("sandbox", context.response().getHeader(HEADER_CONTENT_SECURITY_POLICY));
}

@Test
void testGet_SVG_DisableContentSecurityPolicy() throws Exception {
// disable content security policy for SVG files
underTest = context.registerInjectActivateService(MediaFileServlet.class,
"svgContentSecurityPolicy", false);

context.currentResource(context.load().binaryFile("/filetype/sample.svg", "/content/sample.svg"));

underTest.service(context.request(), context.response());

assertEquals(HttpServletResponse.SC_OK, context.response().getStatus());
assertEquals(ContentType.SVG, context.response().getContentType());
assertEquals(EXPECTED_CONTENT_LENGTH_SVG, context.response().getOutput().length);
assertEquals(EXPECTED_CONTENT_LENGTH_SVG, context.response().getContentLength());
// no CSP
assertNull(context.response().getHeader(HEADER_CONTENT_SECURITY_POLICY));
}

@Test
Expand Down

0 comments on commit 9e1fe9c

Please sign in to comment.