diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java index c55597b47..557c77c76 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/OverlayImageController.java @@ -25,6 +25,7 @@ import stirling.software.common.annotations.api.MiscApi; import stirling.software.common.enumeration.ResourceWeight; import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.util.GeneralUtils; +import stirling.software.common.util.SvgSanitizer; import stirling.software.common.util.TempFile; import stirling.software.common.util.TempFileManager; import stirling.software.common.util.WebResponseUtils; @@ -36,6 +37,7 @@ public class OverlayImageController { private final CustomPDFDocumentFactory pdfDocumentFactory; private final TempFileManager tempFileManager; + private final SvgSanitizer svgSanitizer; @AutoJobPostMapping( consumes = MediaType.MULTIPART_FORM_DATA_VALUE, @@ -61,6 +63,9 @@ public class OverlayImageController { byte[] imageBytes = imageFile.getBytes(); boolean isSvg = SvgOverlayUtil.isSvgImage(imageBytes); + if (isSvg) { + imageBytes = svgSanitizer.sanitize(imageBytes); + } try (PDDocument document = pdfDocumentFactory.load(pdfBytes)) { int pages = document.getNumberOfPages(); diff --git a/app/core/src/main/java/stirling/software/SPDF/utils/SvgOverlayUtil.java b/app/core/src/main/java/stirling/software/SPDF/utils/SvgOverlayUtil.java index 253d9c687..b58bb055f 100644 --- a/app/core/src/main/java/stirling/software/SPDF/utils/SvgOverlayUtil.java +++ b/app/core/src/main/java/stirling/software/SPDF/utils/SvgOverlayUtil.java @@ -10,6 +10,7 @@ import org.apache.batik.bridge.GVTBuilder; import org.apache.batik.bridge.UserAgent; import org.apache.batik.bridge.UserAgentAdapter; import org.apache.batik.gvt.GraphicsNode; +import org.apache.batik.util.ParsedURL; import org.apache.batik.util.XMLResourceDescriptor; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; @@ -39,7 +40,16 @@ public class SvgOverlayUtil { svgDoc = factory.createSVGDocument("file:///overlay.svg", inputStream); } - UserAgent userAgent = new UserAgentAdapter(); + UserAgent userAgent = + new UserAgentAdapter() { + @Override + public void checkLoadExternalResource( + ParsedURL resourceURL, ParsedURL docURL) { + throw new SecurityException( + "External resource loading is disabled for SVG overlays: " + + resourceURL); + } + }; DocumentLoader loader = new DocumentLoader(userAgent); BridgeContext ctx = new BridgeContext(userAgent, loader); ctx.setDynamicState(BridgeContext.DYNAMIC); diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/OverlayImageControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/OverlayImageControllerTest.java index 308518e8f..6a13fb304 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/OverlayImageControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/misc/OverlayImageControllerTest.java @@ -32,6 +32,7 @@ import org.springframework.mock.web.MockMultipartFile; import stirling.software.SPDF.model.api.misc.OverlayImageRequest; import stirling.software.common.service.CustomPDFDocumentFactory; +import stirling.software.common.util.SvgSanitizer; import stirling.software.common.util.TempFile; import stirling.software.common.util.TempFileManager; import stirling.software.common.util.WebResponseUtils; @@ -52,6 +53,7 @@ class OverlayImageControllerTest { @Mock private CustomPDFDocumentFactory pdfDocumentFactory; @Mock private TempFileManager tempFileManager; + @Mock private SvgSanitizer svgSanitizer; @InjectMocks private OverlayImageController controller; @@ -205,6 +207,52 @@ class OverlayImageControllerTest { mockDoc.close(); } + @Test + void overlayImage_svgInput_sanitizedBeforeOverlay() throws Exception { + byte[] maliciousSvg = + ("" + + "" + + "") + .getBytes(); + byte[] sanitized = + ("" + + "" + + "") + .getBytes(); + when(svgSanitizer.sanitize(maliciousSvg)).thenReturn(sanitized); + + MockMultipartFile svgFile = + new MockMultipartFile("imageFile", "overlay.svg", "image/svg+xml", maliciousSvg); + OverlayImageRequest request = new OverlayImageRequest(); + request.setFileInput(pdfFile); + request.setImageFile(svgFile); + request.setX(0); + request.setY(0); + request.setEveryPage(false); + + PDDocument mockDoc = new PDDocument(); + mockDoc.addPage(new PDPage(PDRectangle.A4)); + when(pdfDocumentFactory.load(any(byte[].class))).thenReturn(mockDoc); + + try (MockedStatic mockedWebResponse = + mockStatic(WebResponseUtils.class)) { + mockedWebResponse + .when( + () -> + WebResponseUtils.pdfFileToWebResponse( + any(TempFile.class), anyString())) + .thenReturn(streamingOk("result".getBytes())); + + controller.overlayImage(request); + } + mockDoc.close(); + + verify(svgSanitizer).sanitize(maliciousSvg); + } + @Test void overlayImage_withCoordinates_usesXY() throws Exception { OverlayImageRequest request = new OverlayImageRequest();