一个快速且易于配置、用 Java 编写的 HTML 清理器,可让你 在 Web 应用中包含由第三方编写的 HTML, 同时防范 XSS。
现有依赖是 JSR 305。其他 jar 仅供测试套件使用。JSR 305 依赖是 仅编译期依赖,只用于注解。
此代码的编写秉持安全最佳实践,拥有 广泛的测试套件,并已通过 对抗性安全审查。
快速入门包含无论是否使用 Maven 都能开始使用的说明。
你可以使用 预置策略:
PolicyFactory policy = Sanitizers.FORMATTING.and(Sanitizers.LINKS);
String safeHTML = policy.sanitize(untrustedHTML);
PolicyFactory policy = new HtmlPolicyBuilder()
.allowElements("a")
.allowUrlProtocols("https")
.allowAttributes("href").onElements("a")
.requireRelNofollowOnLinks()
.toFactory();
String safeHTML = policy.sanitize(untrustedHTML);
你可以编写
自定义策略
来实现诸如将 h1 改为带特定 class 的 div 之类的操作:
PolicyFactory policy = new HtmlPolicyBuilder()
.allowElements("p")
.allowElements(
(String elementName, List<String> attrs) -> {
// Add a class attribute.
attrs.add("class");
attrs.add("header-" + elementName);
// Return elementName to include, null to drop.
return "div";
}, "h1", "h2", "h3", "h4", "h5", "h6")
.toFactory();
String safeHTML = policy.sanitize(untrustedHTML);
请注意,如果你希望元素 "a"、"font"、"img"、"input" 和 "span"
在不包含任何属性时也能通过过滤器,
则必须使用 allowWithoutAttributes() 方法
将它们显式加入白名单。
属性策略同样允许运行自定义代码。添加属性策略不会削弱任何默认策略,例如 style 或 URL 属性检查。
new HtmlPolicyBuilder = new HtmlPolicyBuilder()
.allowElement("div", "span")
.allowAttributes("data-foo")
.matching(
(String elementName, String attributeName, String value) -> {
// Return value for the attribute or null to drop.
})
.onElements("div", "span")
.build()
预处理器允许插入文本以及进行大规模的结构性变更。
new HtmlPolicyBuilder = new HtmlPolicyBuilder()
// Use a preprocessor to be backwards compatible with the
// <plaintext> element which
.withPreprocessor(
(HtmlStreamEventReceiver r) -> {
// Provide user with info about links before they click.
// Before: <a href="https://example.com/...">
// After: (https://example.com) <a href="https://example.com/...">
return new HtmlStreamEventReceiverWrapper(r) {
@Override public void openTag(String elementName, List<String> attrs) {
if ("a".equals(elementName)) {
for (int i = 0, n = attrs.size(); i < n; i += 2) {
if ("href".equals(attrs.get(i)) {
String url = attrs.get(i + 1);
String origin;
try {
URI uri = new URI(url);
String scheme = uri.getScheme();
String authority = uri.getRawAuthority();
if (scheme == null && authority == null) {
origin = null;
} else {
origin = (scheme != null ? scheme + ":" : "")
+ (authority != null ? "//" + authority : "");
}
} catch (URISyntaxException ex) {
origin = "about:invalid";
}
if (origin != null) {
text(" (" + origin + ") ");
}
}
}
}
super.openTag(elementName, attrs);
}
};
}
.allowElement("a")
...
.build()
预处理发生在策略应用之前,因此不会影响 输出的安全性。
当策略拒绝某个元素或属性时,它会通知一个 HtmlChangeListener。
你可以借此跟踪策略违规趋势,并发现何时有人在 试图突破你的安全防护。
PolicyFactory myPolicyFactory = ...;
// If you need to associate reports with some context, you can do so.
MyContextClass myContext = ...;
String sanitizedHtml = myPolicyFactory.sanitize(
unsanitizedHtml,
new HtmlChangeListener<MyContextClass>() {
@Override
public void discardedTag(MyContextClass context, String elementName) {
// ...
}
@Override
public void discardedAttributes(
MyContextClass context, String elementName, String... attributeNames) {
// ...
}
},
myContext);
注意:如果某个字符串在清理时未产生任何变更通知, 这并不意味着输入字符串就一定可以安全使用。 请只使用清理器的输出。
清理器确保输出属于常用 HTML 解析器 对其含义能够达成一致的 HTML 子集,但没有任何通知 并不意味着输入就属于这样的子集, 只能说明其中不包含被移除的元素或属性。
有关此主题的更多信息,请参阅《既然可以验证,为何还要清理?》。
如果你想报告漏洞,请参阅 AttackReviewGroundRules。
如果你想参与贡献,请联系 @mvsamuel 或 @manicode。
我们欢迎问题报告和 PR。 改变行为或新增功能的 PR 应同时包含正向测试和 负向测试。
请注意,贡献内容遵循 Apache 2.0 许可证。