高速で設定が簡単なHTMLサニタイザー。Javaで書かれており、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を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にpingを送ってください。
問題報告とPRを歓迎します。動作を変更したり機能を追加するPRには、ポジティブテストとネガティブテストの両方を含める必要があります。
貢献はApache 2.0ライセンスの下で行われることに注意してください。
批判やコードで助けてくれたすべての人に感謝します。