diff --git a/server/src/main/java/cn/keking/web/controller/OnlinePreviewController.java b/server/src/main/java/cn/keking/web/controller/OnlinePreviewController.java index 666e322b..3d5e4d15 100644 --- a/server/src/main/java/cn/keking/web/controller/OnlinePreviewController.java +++ b/server/src/main/java/cn/keking/web/controller/OnlinePreviewController.java @@ -86,8 +86,8 @@ public class OnlinePreviewController { highlightall= KkFileUtils.htmlEscape(highlightall); model.addAttribute("highlightall", highlightall); - model.addAttribute("page", page); - model.addAttribute("kkagent", kkagent); + model.addAttribute("page", normalizePage(page)); + model.addAttribute("kkagent", Boolean.toString(Boolean.parseBoolean(kkagent))); model.addAttribute("file", fileAttribute); FilePreview filePreview = previewFactory.get(fileAttribute); logger.info("预览文件url:{},previewType:{}", fileUrl, fileAttribute.getType()); @@ -98,6 +98,17 @@ public class OnlinePreviewController { return filePreview.filePreviewHandle(fileUrl, model, fileAttribute); //统一在这里处理 url } + private static String normalizePage(String page) { + if (page != null && page.matches("[0-9]{1,10}")) { + try { + return Integer.toString(Integer.parseInt(page)); + } catch (NumberFormatException ignored) { + // Out-of-range values use the same default as missing values. + } + } + return "0"; + } + @GetMapping( "/picturesPreview") public String picturesPreview(@RequestParam String urls, @RequestParam(required = false) String key, @@ -287,4 +298,4 @@ public class OnlinePreviewController { cacheService.addQueueTask(fileUrls); return "success"; } -} \ No newline at end of file +} diff --git a/server/src/test/java/cn/keking/web/controller/PreviewParameterSecurityTests.java b/server/src/test/java/cn/keking/web/controller/PreviewParameterSecurityTests.java new file mode 100644 index 00000000..4f8b4f15 --- /dev/null +++ b/server/src/test/java/cn/keking/web/controller/PreviewParameterSecurityTests.java @@ -0,0 +1,84 @@ +package cn.keking.web.controller; + +import cn.keking.config.ConfigConstants; +import cn.keking.model.FileAttribute; +import cn.keking.service.FileHandlerService; +import cn.keking.service.FilePreview; +import cn.keking.service.FilePreviewFactory; +import freemarker.template.Configuration; +import jakarta.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.ui.ExtendedModelMap; + +import java.io.StringWriter; +import java.util.Base64; + +import static org.junit.jupiter.api.Assertions.*; + +class PreviewParameterSecurityTests { + private String previousKey; + @BeforeEach void setup() { + previousKey = ConfigConstants.getKey(); + ConfigConstants.setKeyValue("false"); + } + @AfterEach void restore() { ConfigConstants.setKeyValue(previousKey); } + + private ExtendedModelMap preview(String page, String agent) { + FileHandlerService handler = new FileHandlerService(null) { + @Override public FileAttribute getFileAttribute(String url, HttpServletRequest request) { + FileAttribute file = new FileAttribute(); + file.setName("document.pdf"); + file.setSuffix("pdf"); + return file; + } + }; + FilePreviewFactory factory = new FilePreviewFactory(null) { + @Override public FilePreview get(FileAttribute file) { + return (url, model, attribute) -> { + model.addAttribute("pdfUrl", url); + return "pdf"; + }; + } + }; + ExtendedModelMap model = new ExtendedModelMap(); + OnlinePreviewController controller = new OnlinePreviewController(factory, handler, null, null); + String url = Base64.getEncoder().encodeToString("https://files.example/document.pdf".getBytes()); + assertEquals("pdf", controller.onlinePreview(url, null, null, "false", page, agent, + model, new MockHttpServletRequest())); + return model; + } + + @ParameterizedTest + @ValueSource(strings = {"';alert(1);//", "\";alert(1);//", "1", + "1;alert(1)", "-1", "2147483648", "999999999999999999999"}) + void rendersOnlySafeScalarValuesThroughControllerAndPdfTemplate(String payload) throws Exception { + ExtendedModelMap model = preview(payload, payload); + assertEquals("0", model.get("page")); + assertEquals("false", model.get("kkagent")); + model.put("baseUrl", "https://preview.example/"); + for (String name : new String[]{"watermarkXSpace", "watermarkYSpace", "watermarkAlpha", + "watermarkWidth", "watermarkHeight", "watermarkAngle"}) { + model.put(name, "0"); + } + Configuration cfg = new Configuration(Configuration.VERSION_2_3_32); + cfg.setClassForTemplateLoading(getClass(), "/web"); + cfg.setClassicCompatible(true); + StringWriter output = new StringWriter(); + cfg.getTemplate("pdf.ftl").process(model, output); + String html = output.toString(); + assertTrue(html.contains("var kkagent = 'false';")); + assertTrue(html.contains("#page=0")); + assertFalse(html.contains("alert(1)")); + } + + @Test void preservesSupportedValues() { + assertEquals("12", preview("0012", "false").get("page")); + assertEquals("2147483647", preview("2147483647", "true").get("page")); + assertEquals("true", preview("1", "true").get("kkagent")); + } +}