对于我们的应用程序,我们允许用户输入 HTML,稍后会向其他用户显示。
出于安全原因,我们会清理此 HTML,并且不允许用户保存 HTML,这在其他用户浏览器中运行可能不安全。
为此,我们有这两种方法(简单来说,它们做得更多,但这就是这个问题的本质):
public static String sanitizeHTML(String html) {
return Jsoup.clean(html,
"",
Safelist.relaxed()
.addAttributes(":all", "style")
.addAttributes(":all", "class"),
new Document.OutputSettings().prettyPrint(false));
}
和
public static boolean isHTMLStringPolluted(String html) {
return !sanitizeHTML(html).equals(html);
}
我们使用
isHTMLStringPolluted
来验证用户输入。
如果用户现在输入
<a href="https://www.stackoverflow.com">Link</a>
,那完全没问题。
如果用户输入 <a href='https://www.stackoverflow.com'>Link</a>
,该方法将返回 false,因为 sanitizeHTML 方法返回 <a href="https://www.stackoverflow.com">Link</a>
。
这只是这个问题最简单的例子之一。用户可以添加更复杂的 HTML。此外,它们不仅有 HTML 编辑器,还可以通过非常复杂的数学语言(与 Excel 公式相当)创建、计算和连接这些 HTML 片段,该语言在整个应用程序中使用数据、变量和其他 HTML 输出。这个 HTML 只是结果。
我们不想强迫用户只使用双引号,我们也不希望用双引号替换单引号,以确保原始用户输入要么完全接受,要么完全拒绝。
有没有办法配置jsoup以保持报价的原样?
我还使用了不同的库,例如 OWASP Java HTML Sanitizer,但它有更多限制和缺陷,不符合我们的要求。
jsoup 中没有设置会在属性值周围保留单引号。
我会以不同的方式处理这个问题 - 而不是尝试比较精确的输入和输出相等性,而是使用
Cleaner.isValidBodyHtml()
方法。检查输入的 HTML 是否不包含任何解析错误,并且 Cleaner 没有删除任何不在 Safelist 中的元素或属性。
无论该方法的输出如何,您都应该只使用/保留清理后的输出,而不是原始输入。
就我个人而言,我通常会使用
isValid()
作为 UI 指示器,而不是在其基础上进行阻塞——因为无论输入如何,清理后的输出都是安全的并且具有平衡的标签。没有理由阻止,例如只是因为用户错过了结束标签。但这种行为显然取决于您的具体用例。
这是一个有效的例子:
Safelist safelist = Safelist.relaxed()
.addAttributes(":all", "style")
.addAttributes(":all", "class");
Cleaner cleaner = new Cleaner(safelist);
String input = "<a href='https://www.stackoverflow.com'>Link</a>";
boolean isValid = cleaner.isValidBodyHtml(input);
print("isValid?", String.valueOf(isValid));
Document cleanDoc = cleaner.clean(Jsoup.parse(input));
print("Cleaned", cleanDoc.body().html());
给予:
isValid?: true
Cleaned: <a href="https://www.stackoverflow.com">Link</a>
还有
的输入String input =
"<a href='https://www.stackoverflow.com' class=ok>Link<script>xss()</script>";
给予:
isValid?: false
Cleaned: <a href="https://www.stackoverflow.com" class="ok">Link</a>