From fb2841784bb0da661ae3a4350eb246c502350fd7 Mon Sep 17 00:00:00 2001 From: AprilWind <2100166581@qq.com> Date: Wed, 13 Aug 2025 13:24:31 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E6=96=B0=E5=A2=9E=20HTML/XSS=20?= =?UTF-8?q?=E8=BF=87=E6=BB=A4=E5=B7=A5=E5=85=B7=E7=B1=BB=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=A4=9A=E7=A7=8D=E8=BF=87=E6=BB=A4=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 7 + ruoyi-common/ruoyi-common-core/pom.xml | 5 + .../common/core/utils/HtmlSanitizerUtil.java | 165 ++++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/HtmlSanitizerUtil.java diff --git a/pom.xml b/pom.xml index 7aabad563..59acf96f8 100644 --- a/pom.xml +++ b/pom.xml @@ -36,6 +36,7 @@ 0.2.0 1.18.38 1.80 + 20240325.1 1.16.7 2.7.0 @@ -288,6 +289,12 @@ ${bouncycastle.version} + + com.googlecode.owasp-java-html-sanitizer + owasp-java-html-sanitizer + ${owasp.html-sanitizer.version} + + io.github.linpeilie mapstruct-plus-spring-boot-starter diff --git a/ruoyi-common/ruoyi-common-core/pom.xml b/ruoyi-common/ruoyi-common-core/pom.xml index ad37e90db..6048398f3 100644 --- a/ruoyi-common/ruoyi-common-core/pom.xml +++ b/ruoyi-common/ruoyi-common-core/pom.xml @@ -94,6 +94,11 @@ ip2region + + com.googlecode.owasp-java-html-sanitizer + owasp-java-html-sanitizer + + diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/HtmlSanitizerUtil.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/HtmlSanitizerUtil.java new file mode 100644 index 000000000..a1f8ba51d --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/HtmlSanitizerUtil.java @@ -0,0 +1,165 @@ +package org.dromara.common.core.utils; + +import org.owasp.html.HtmlPolicyBuilder; +import org.owasp.html.PolicyFactory; +import org.owasp.html.Sanitizers; + +/** + * HTML/XSS 过滤工具类 + * 基于 OWASP Java HTML Sanitizer + * + * @author AprilWind + */ +public class HtmlSanitizerUtil { + /** + * 纯文本策略: + * 去除所有 HTML 标签,仅保留纯文本内容, + * 适用于对内容格式无任何要求,只需要纯文本的场景,如用户名、标题等 + */ + public static final PolicyFactory TEXT_ONLY_POLICY = new HtmlPolicyBuilder().toFactory(); + + /** + * 简单展示文本策略: + * 允许部分格式化标签,介于纯文本和富文本之间, + * 适用于简单文本展示,如昵称、简短描述等 + */ + public static final PolicyFactory SIMPLE_TEXT_POLICY = Sanitizers.FORMATTING; + + /** + * 严格无脚本策略: + *

+ * 仅允许非常基础的格式化标签,如加粗(b/strong)、斜体(i/em)、下划线(u)、 + * 段落(p)、换行(br)和列表(ul/ol/li)等,禁止所有标签属性, + * 包括样式(style)、事件处理器(onXXX)或其他可能引入 XSS 的内容 + *

+ *

+ * 适用于对安全要求极高且不需要任何复杂格式的场景, + * 如纯文本内容的最小格式化需求 + *

+ */ + public static final PolicyFactory STRICT_NO_SCRIPT_POLICY = new HtmlPolicyBuilder() + .allowElements("b", "strong", "i", "em", "u", "p", "br", "ul", "ol", "li") + // 显式声明允许文本出现在这些标签中,增强策略清晰度(可选) + .allowTextIn("b", "strong", "i", "em", "u", "p", "ul", "ol", "li") + .toFactory(); + + /** + * 评论区策略: + * 允许基础格式化标签和超链接, + * 防止图片、表格等复杂内容, + * 适用于评论区文本,既保证安全,又支持简单的文本格式和链接 + */ + public static final PolicyFactory COMMENT_POLICY = Sanitizers.FORMATTING + .and(Sanitizers.LINKS); + + /** + * 文章编辑器策略: + * 允许格式化标签、超链接、图片、样式、块级元素和表格, + * 适用于富文本编辑器内容,如文章正文,兼顾内容丰富和安全 + */ + public static final PolicyFactory ARTICLE_POLICY = Sanitizers.FORMATTING + .and(Sanitizers.LINKS) + .and(Sanitizers.IMAGES) + .and(Sanitizers.STYLES) + .and(Sanitizers.BLOCKS) + .and(Sanitizers.TABLES); + + /** + * 富文本邮件内容策略: + * 允许基础格式化标签、超链接、图片、表格和样式, + * 相较于一般富文本编辑器稍微严格一些,主要用于邮件内容的安全过滤, + * 防止恶意脚本注入,同时保证邮件内容的丰富展示效果 + */ + public static final PolicyFactory EMAIL_HTML_POLICY = Sanitizers.FORMATTING + .and(Sanitizers.LINKS) + .and(Sanitizers.IMAGES) + .and(Sanitizers.STYLES) + .and(Sanitizers.TABLES); + + /** + * CMS 编辑器策略:较宽松,允许多种标签和属性 + * + *

该策略适用于内容管理系统中的富文本编辑器,支持多样化的HTML标签和常用属性, + * 包括超链接、图片、表格、媒体标签等,同时允许安全的样式和部分自定义属性。

+ * + * + */ + public static final PolicyFactory CMS_EDITOR_POLICY = new HtmlPolicyBuilder() + .allowElements( + "a", "b", "blockquote", "br", "caption", "cite", "code", + "col", "colgroup", "dd", "div", "dl", "dt", "em", "h1", + "h2", "h3", "h4", "h5", "h6", "i", "img", "li", "ol", + "p", "pre", "q", "small", "strike", "strong", "sub", + "sup", "table", "tbody", "td", "tfoot", "th", "thead", + "tr", "u", "ul", "span", "iframe", "video", "audio", "source") + .allowAttributes("href").onElements("a") + .allowAttributes("src", "alt", "title", "width", "height", "type", "controls").onElements("img", "iframe", "video", "audio", "source") + .allowAttributes("colspan", "rowspan").onElements("td", "th") + .allowStyling() // 允许安全的内联样式,过滤危险CSS + .allowUrlProtocols("http", "https", "mailto") // 限制URL协议,防止注入javascript等 + .allowAttributes("data-toggle", "data-target", "data-id").onElements("*") // 支持部分自定义 data-* 属性,方便前端交互 + .allowAttributes("aria-label", "aria-hidden", "aria-expanded", "aria-pressed").onElements("*") // 支持无障碍辅助属性 + .allowAttributes("role").onElements("*") // 支持无障碍role属性 + .allowAttributes("allowfullscreen", "frameborder").onElements("iframe") // iframe 支持全屏及边框属性 + .toFactory(); + + /** + * 纯文本过滤,去除所有 HTML 标签 + */ + public static String sanitizeToText(String htmlContent) { + return htmlContent == null ? null : TEXT_ONLY_POLICY.sanitize(htmlContent); + } + + /** + * 简单文本过滤,允许部分格式 + */ + public static String sanitizeForSimpleText(String htmlContent) { + return htmlContent == null ? null : SIMPLE_TEXT_POLICY.sanitize(htmlContent); + } + + /** + * 严格无脚本过滤,只允许非常基础标签,无任何属性 + */ + public static String sanitizeStrictNoScript(String htmlContent) { + return htmlContent == null ? null : STRICT_NO_SCRIPT_POLICY.sanitize(htmlContent); + } + + /** + * 评论区 HTML 过滤 + */ + public static String sanitizeForComment(String htmlContent) { + return htmlContent == null ? null : COMMENT_POLICY.sanitize(htmlContent); + } + + /** + * 文章编辑器 HTML 过滤 + */ + public static String sanitizeForArticle(String htmlContent) { + return htmlContent == null ? null : ARTICLE_POLICY.sanitize(htmlContent); + } + + /** + * 邮件富文本过滤 + */ + public static String sanitizeForEmailHtml(String htmlContent) { + return htmlContent == null ? null : EMAIL_HTML_POLICY.sanitize(htmlContent); + } + + /** + * CMS 编辑器过滤,允许较多标签及属性 + */ + public static String sanitizeForCmsEditor(String htmlContent) { + return htmlContent == null ? null : CMS_EDITOR_POLICY.sanitize(htmlContent); + } + +} -- Gitee