? ? ? ? 有這樣一個場景,首先構建一個docx文件并插入超鏈接(惡意的鏈接),上傳到文件服務器后獲取對應的文件filekey。現在我們提供一個預覽接口,通過filekey便可以預覽,在根據filekey轉html文檔返回給頁面的時候由于插入的超鏈接成功觸發XSS攻擊;
? ? ? ? 初始預覽接口代碼示例如下:
@GetMapping(value = "/wordConvertHtml/{fileKey}")@ApiImplicitParam(name = "fileKey", value = "文件唯一key", required = true)public void wordConvertHtml(@PathVariable String fileKey, HttpServletResponse response) throws IOException {OutputStream bos = null;try {InputStreamVO inputStreamVO = commonFileService.getInputStreamWithAuthCheck(fileKey);Document doc = new Document(inputStreamVO.getInputStream());HtmlSaveOptions opts = new HtmlSaveOptions(SaveFormat.HTML);opts.setExportXhtmlTransitional(true);opts.setExportImagesAsBase64(true);opts.setExportPageSetup(true);opts.setPrettyFormat(true);}catch (FileCorruptedException e) {log.error("word轉換HTML發生特定異常:{}", e);// 給前端提示特殊信息PrintWriter out = new PrintWriter(bos);out.println("文件含有特殊格式,系統不兼容。");out.flush();out.close();} catch (Exception e) {log.error("word轉換HTML異常", e);// 給前端提示信息response.setContentType("text/html; charset=UTF-8");PrintWriter out = new PrintWriter(bos);out.println(e.getMessage());out.flush();out.close();} finally {if (bos != null) {bos.close();}}}
處理方式:
? ? ? ?添加更全面的安全頭設置:確保所有可能返回HTML內容的路徑都設置了適當的安全頭
1.Content-Security-Policy (CSP)
response.setHeader("Content-Security-Policy", "default-src 'none'; script-src 'none'; style-src 'unsafe-inline'; img-src data:;");
這是一個重要的安全特性,用于防止跨站腳本攻擊(XSS)、點擊劫持等攻擊:
- default-src 'none': 默認情況下不允許從任何源加載資源
- script-src 'none': 不允許執行任何JavaScript代碼
- style-src 'unsafe-inline': 允許內聯樣式(如<style>標簽和style屬性)
- img-src data:: 只允許通過data URI加載圖片
這種配置可以有效防止惡意腳本注入,同時允許內聯樣式和內嵌圖片顯示。
2.X-Content-Type-Options
response.setHeader("X-Content-Type-Options", "nosniff");
防止瀏覽器嘗試猜測或"嗅探"響應的內容類型:
- 瀏覽器會嚴格按照服務器提供的Content-Type頭來處理內容
- 防止某些類型的攻擊,比如將JavaScript文件當作圖片顯示
3.X-Frame-Options
response.setHeader("X-Frame-Options", "DENY");
防止頁面被嵌入到iframe中,用于防止點擊劫持攻擊:
- DENY: 頁面不能被嵌入到任何iframe中
- 這可以防止惡意網站將你的頁面嵌入到他們的網站中進行釣魚攻擊
4.X-XSS-Protection
response.setHeader("X-XSS-Protection", "1; mode=block");
啟用瀏覽器的內置XSS過濾器:
- 1: 啟用XSS過濾
- mode=block: 當檢測到XSS攻擊時,瀏覽器會阻止整個頁面加載,而不是僅僅過濾掉可疑內容
最后統一對錯誤進行處理,這樣安全等級會更高。
@GetMapping(value = "/wordConvertHtml/{fileKey}")
@ApiImplicitParam(name = "fileKey", value = "文件唯一key", required = true)
public void wordConvertHtml(@PathVariable String fileKey, HttpServletResponse response) throws IOException {OutputStream bos = null;try {bos = response.getOutputStream();response.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline';");response.setHeader("X-Content-Type-Options", "nosniff");response.setHeader("X-Frame-Options", "DENY");response.setHeader("X-XSS-Protection", "1; mode=block");InputStreamVO inputStreamVO = commonFileService.getInputStreamWithAuthCheck(fileKey);Document doc = new Document(inputStreamVO.getInputStream());HtmlSaveOptions opts = new HtmlSaveOptions(SaveFormat.HTML);opts.setExportXhtmlTransitional(true);opts.setExportImagesAsBase64(true);opts.setExportPageSetup(true);opts.setPrettyFormat(true);doc.save(bos, opts);} catch (FileCorruptedException e) {log.error("word轉換HTML發生特定異常:{}", e);// 給前端提示特殊信息// 設置安全響應頭response.setContentType("text/html; charset=UTF-8");response.setHeader("Content-Security-Policy", "default-src 'none'; script-src 'none';");response.setHeader("X-Content-Type-Options", "nosniff");response.setHeader("X-Frame-Options", "DENY");PrintWriter out = new PrintWriter(bos);out.println("文件含有特殊格式,系統不兼容。");out.flush();out.close();} catch (Exception e) {log.error("word轉換HTML異常", e);// 給前端提示信息// 設置安全響應頭response.setContentType("text/html; charset=UTF-8");response.setHeader("Content-Security-Policy", "default-src 'none'; script-src 'none';");response.setHeader("X-Content-Type-Options", "nosniff");response.setHeader("X-Frame-Options", "DENY");PrintWriter out = new PrintWriter(bos);out.println(HtmlUtils.htmlEscape(e.getMessage()));out.flush();out.close();} finally {if (bos != null) {bos.close();}}
}