bluemonday は Go で実装された HTML サニタイザーです。高速で、高度に設定可能です。
bluemonday は、信頼できないユーザー生成コンテンツを入力として受け取り、承認された HTML 要素と属性の許可リストに基づいてサニタイズされた HTML を返すため、そのコンテンツを Web ページに安全に含めることができます。
ユーザー生成コンテンツを受け入れており、サーバーが Go を使用しているなら、bluemonday が必要です。
ユーザー生成コンテンツのデフォルトポリシー(bluemonday.UGCPolicy().Sanitize())は、これを:
Hello <STYLE>.XSS{background-image:url("javascript:alert('XSS')");}</STYLE><A CLASS=XSS></A>World
この無害なものに変換します:
Hello World
また、これは:
<a href="javascript:alert('XSS1')" onmouseover="alert('XSS2')">XSS<a>
次のように変換されます:
XSS
一方、これは引き続き許可されます:
<a href="http://www.google.com/">
<img src="https://ssl.gstatic.com/accounts/ui/logo_2x.png"/>
</a>
ほとんど変更されずに通過します(ユーザー生成コンテンツにとって良いことである rel="nofollow" が追加されます):
<a href="http://www.google.com/" rel="nofollow">
<img src="https://ssl.gstatic.com/accounts/ui/logo_2x.png"/>
</a>
これにより、サイトを XSS 攻撃から保護します。XSS 攻撃のベクター は多数存在し、リスクを軽減する最善の方法は、既知の安全な HTML 要素と属性のリストに対してユーザー入力をサニタイズすることです。
bluemonday は、他の処理の後に常に実行してください。
blackfriday や Pandoc を使用する場合、これらのステップの後に bluemonday を実行する必要があります。これにより、プロセスの後半で安全でない HTML が挿入されることを防ぎます。
bluemonday は、OWASP Java HTML Sanitizer と HTML Purifier の両方から大きな影響を受けています。
許可リストベースであり、許可する HTML 要素と属性(および属性の regexp パターン)を記述したポリシーを構築するか、適切なデフォルトを表す提供済みポリシーのいずれかを使用する必要があります。
許可リストを含むポリシーは、Go コアチームによって実装された Go net/html ライブラリ の高速な非検証・前方向のみ・トークンベースのパーサーを使用して適用されます。
適切にフォーマットされた HTML(該当するすべての開始要素に対する終了要素、正しいネスト)が提供されることを想定しているため、不正なネストや不完全な HTML の修復には焦点を当てていません。既存の要素がポリシーの許可リストに記載されていること、および属性とリンクが Web ページで安全に使用できることを確認することにのみ焦点を当てています。GIGO が適用され、不正な HTML を入力した場合、bluemonday はそれを再び適切なものにする方法を考える役割は負いません。
はい
広く使用され、十分に現場テストされた OWASP Java HTML Sanitizer から移行し、本番環境で bluemonday を使用しています。
広範なテストスイート(AntiSamy テストや、提起された問題に対するテストを含む)をすべて通過しています。未解決の問題 を確認して、あなたにとってのブロッカーとなるものがないかどうかを確認してください。
ユーザー生成コンテンツを介したさまざまな攻撃に対して包括的な保護を提供できるよう、プルリクエストと issue をお待ちしています。
go get github.com/microcosm-cc/bluemonday でインストールします
次に、次のように呼び出します:
package main
import (
"fmt"
"github.com/microcosm-cc/bluemonday"
)
func main() {
// Do this once for each unique policy, and use the policy for the life of the program
// Policy creation/editing is not safe to use in multiple goroutines
p := bluemonday.UGCPolicy()
// The policy can then be used to sanitize lots of input and it is safe to use the policy in multiple goroutines
html := p.Sanitize(
`<a onblur="alert(secret)" href="http://www.google.com">Google</a>`,
)
// Output:
// <a href="http://www.google.com" rel="nofollow">Google</a>
fmt.Println(html)
}
Sanitize を呼び出す方法は 3 つあります:
p.Sanitize(string) string
p.SanitizeBytes([]byte) []byte
p.SanitizeReader(io.Reader) bytes.Buffer
パフォーマンスにこだわる場合、p.SanitizeReader(r).Bytes() は入力または出力の不要なキャストを行うことなく []byte を返します。ただし、その差はごくわずかなので、気にする必要はありません。
独自のポリシーを構築できます:
package main
import (
"fmt"
"github.com/microcosm-cc/bluemonday"
)
func main() {
p := bluemonday.NewPolicy()
// Require URLs to be parseable by net/url.Parse and either:
// mailto: http:// or https://
p.AllowStandardURLs()
// We only allow <p> and <a href="">
p.AllowAttrs("href").OnElements("a")
p.AllowElements("p")
html := p.Sanitize(
`<a onblur="alert(secret)" href="http://www.google.com">Google</a>`,
)
// Output:
// <a href="http://www.google.com">Google</a>
fmt.Println(html)
}
2 つのデフォルトポリシーを提供しています:
bluemonday.StrictPolicy() は、許可リストに何もないため、すべての HTML 要素とその属性を除去することと同等と考えられます。使用例としては、HTML タグがまったく期待されず、もし存在する場合は要素と要素のコンテンツの両方を除去すべきブログ投稿のタイトルが挙げられます。これは非常に厳格なポリシーです。bluemonday.UGCPolicy() は、ユーザー生成コンテンツに対して安全な幅広い HTML 要素と属性を許可します。このポリシーは iframe、object、embed、styles、script などを許可しないことに注意してください。使用例としては、さまざまなフォーマットに加えて TABLE や IMG の可能性が期待されるブログ投稿の本文が挙げられます。ポリシー構築の本質は、あなたのシナリオにとってどの HTML 要素と属性が安全であると見なされるかを決定することです。OWASP はリスクの説明に役立つ XSS 防止チートシート を提供していますが、基本的には次のとおりです:
script、style、iframe、object、embed、base 要素を避ける基本的に、あなたのシナリオに適した HTML を説明できる必要があります。ポリシーを説明できる自信がない場合は、bluemonday.UGCPolicy() などの提供済みポリシーのいずれかの使用を検討してください。
新しいポリシーを作成するには:
p := bluemonday.NewPolicy()
ポリシーに要素を追加するには、要素だけを追加するか:
p.AllowElements("b", "strong")
または正規表現を使用します:
注:上記のように名前で要素が追加された場合、一致する正規表現は無視されます
また、実行順序は保証されておらず、一部のルールが見逃される可能性があるため、複数のパターンが重複しないようにすることも推奨されます。
p.AllowElementsMatching(regex.MustCompile(`^my-element-`))
あるいは、属性を追加することで要素を追加します:
// Note the recommended pattern, see the recommendation on using .Matching() below
p.AllowAttrs("nowrap").OnElements("td", "th")
同様に、これも正規表現パターンマッチの代替をサポートしています:
p.AllowAttrs("nowrap").OnElementsMatching(regex.MustCompile(`^my-element-`))
属性はすべての要素に追加することもできます:
p.AllowAttrs("dir").Matching(regexp.MustCompile("(?i)rtl|ltr")).Globally()
または、属性を特定の要素に追加することもできます:
// Not the recommended pattern, see the recommendation on using .Matching() below
p.AllowAttrs("value").OnElements("li")
属性は常にパターンにマッチさせることが推奨されます。そうしないと、HTML 属性内の XSS が非常に容易になります:
// \p{L} matches unicode letters, \p{N} matches unicode numbers
p.AllowAttrs("title").Matching(regexp.MustCompile(`[\p{L}\p{N}\s\-_',:\[\]!\./\\\(\)&]*`)).Globally()
いつでも停止して .Sanitize() を呼び出すことができます:
// string htmlIn passed in from a HTTP POST
htmlOut := p.Sanitize(htmlIn)
また、既存のポリシーを取得して拡張できます:
p := bluemonday.UGCPolicy()
p.AllowElements("fieldset", "select", "option")
Matching ルールを備えた AllowAttrs を使用してインライン CSS を処理することは可能ですが、許可したいすべてのインライン CSS を安全に処理する単一のモノリシックな正規表現を作成することは簡単な作業ではありません。その代わりに、任意の要素で style 属性を許可し、スタイルポリシーを使用してインラインスタイルを制御およびサニタイズできます。
各スタイルがニーズに合っていることを確認するために、Matching(適切な正規表現を使用)、MatchingEnum、または MatchingHandler を使用することを強くお勧めします。ただし、最も広く使用されているスタイルにはデフォルトのハンドラーが提供されています。
属性と同様に、特定の CSS プロパティがインラインで設定されることを許可できます:
p.AllowAttrs("style").OnElements("span", "p")
// Allow the 'color' property with valid RGB(A) hex values only (on any element allowed a 'style' attribute)
p.AllowStyles("color").Matching(regexp.MustCompile("(?i)^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$")).Globally()
さらに、CSS プロパティが許可された値にのみ設定されることを許可できます:
p.AllowAttrs("style").OnElements("span", "p")
// Allow the 'text-decoration' property to be set to 'underline', 'line-through' or 'none'
// on 'span' elements only
p.AllowStyles("text-decoration").MatchingEnum("underline", "line-through", "none").OnElements("span")
または、regex パターンマッチに基づいて要素を指定できます:
p.AllowAttrs("style").OnElementsMatching(regex.MustCompile(`^my-element-`))
// Allow the 'text-decoration' property to be set to 'underline', 'line-through' or 'none'
// on 'span' elements only
p.AllowStyles("text-decoration").MatchingEnum("underline", "line-through", "none").OnElementsMatching(regex.MustCompile(`^my-element-`))
より詳細なチェックが必要な場合は、文字列を受け取り bool を返すハンドラーを作成して、特定のプロパティの値を検証できます。文字列パラメーターは小文字に変換され、Unicode コードポイントも変換されています。
myHandler := func(value string) bool{
// Validate your input here
return true
}
p.AllowAttrs("style").OnElements("span", "p")
// Allow the 'color' property with values validated by the handler (on any element allowed a 'style' attribute)
p.AllowStyles("color").MatchingHandler(myHandler).Globally()
リンクは安全にサニタイズするのが難しいものであり、悪意のあるコンテンツの最大の攻撃ベクターのひとつでもあります。
これは可能です:
p.AllowAttrs("href").Matching(regexp.MustCompile(`(?i)mailto|https?`)).OnElements("a")
しかし、この場合、正規表現は不正な値が予期しないことを行うのを防ぐには不十分であるため、あなたを保護することはできません。
リンクを安全に扱うための追加のグローバルオプションをいくつか提供しています。
RequireParseableURLs は、URL が Go の net/url パッケージで解析可能であることを保証します:
p.RequireParseableURLs(true)
解析可能な URL を有効にした場合、次のオプションで AllowRelativeURLs が使用できます。デフォルトではこれは無効です(bluemonday は許可リストツールです... 何かを許可するには明示的に指示する必要があります)。無効の場合、すべてのローカルおよびスキーム相対 URL(つまり href="localpage.html"、href="../home.html"、さらには href="//www.google.com" も相対 URL)を防止します:
p.AllowRelativeURLs(true)
解析可能な URL を有効にした場合、許可されるスキーム(http や https を考えるとき、一般にプロトコルと呼ばれる)を許可できます。上記のオプションで相対 URL を許可すると、空のスキームが許可されることに注意してください:
p.AllowURLSchemes("mailto", "http", "https")
解析可能な URL を有効にしたかどうかに関係なく、すべての URL に rel="nofollow" 属性を強制できます。これは存在しない場合に追加されますが、href が有効な場合にのみ追加されます:
// This applies to "a" "area" "link" elements that have a "href" attribute
p.RequireNoFollowOnLinks(true)
同様に、すべての URL の rel 属性に "noreferrer" を含めるように強制できます。
// This applies to "a" "area" "link" elements that have a "href" attribute
p.RequireNoReferrerOnLinks(true)
上記のすべてを適用する便利なメソッドを提供していますが、URL ルールを適用するには、リンク可能な要素を引き続き許可する必要があります:
p.AllowStandardURLs()
p.AllowAttrs("cite").OnElements("blockquote", "q")
p.AllowAttrs("href").OnElements("a", "area")
p.AllowAttrs("src").OnElements("img")
リンクに関する追加の複雑さは、RFC2397 で定義されている data URI です。data URI を使用すると、この形式で画像をインラインで提供できます:
<img src="data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=">
data URI リンクの mimetype とそれに続く base64 コンテンツを検証するヘルパーを提供しています:
p.AllowDataURIImages()
このヘルパーは GIF、JPEG、PNG、WEBP 画像を有効にします。
data URI リンクの使用には潜在的な セキュリティ リスク があることに注意してください。コンテンツをすでに信頼している場合にのみ、data URI リンクを有効にしてください。
ユーザー生成コンテンツの処理に役立つ機能もいくつかあります:
p.AddTargetBlankToFullyQualifiedLinks(true)
これにより、完全修飾されたアンカー <a href="" /> リンク(href の宛先にホスト名が含まれる)に target="_blank" が追加されることが保証されます。
さらに、ポリシーが適用された後に target="_blank" を持つリンクは、rel 属性も noopener を追加するように調整されます。つまり、リンクは <a href="//host/path"/> のように始まり、<a href="//host/path" rel="noopener" target="_blank"> として終わる可能性があります。noopener の追加はセキュリティ機能であり、問題ではないことに注意することが重要です。ブラウザーには、target="_blank" の結果として開かれたブラウザーウィンドウがオープナー(あなたの Web ページ)を制御できるという残念な機能があり、これはそれから保護します。この背景はここにあります:https://dev.to/ben/the-targetblank-vulnerability-by-example
ポリシー構築を簡素化するためのヘルパーもいくつかバンドルしています:
// Permits the "dir", "id", "lang", "title" attributes globally
p.AllowStandardAttributes()
// Permits the "img" element and its standard attributes
p.AllowImages()
// Permits ordered and unordered lists, and also definition lists
p.AllowLists()
// Permits HTML tables and all applicable elements and non-styling attributes
p.AllowTables()
以下は無効です:
// This does not say where the attributes are allowed, you need to add
// .Globally() or .OnElements(...)
// This will be ignored without error.
p.AllowAttrs("value")
// This does not say where the attributes are allowed, you need to add
// .Globally() or .OnElements(...)
// This will be ignored without error.
p.AllowAttrs(
"type",
).Matching(
regexp.MustCompile("(?i)^(circle|disc|square|a|A|i|I|1)$"),
)
両方の例は同じ問題を示しています。属性を宣言していますが、それらがグローバルに許可されるのか、特定の要素(およびどの要素)でのみ許可されるのかを指定していません。属性は 1 つ以上の要素に属するため、ポリシーはこれを宣言する必要があります。
CSS の許可とサニタイズを支援するツールはまだ含まれていません。つまり、単一の正規表現で重い処理を行いたい場合(推奨されません)を除き、どこでも "style" 属性を許可すべきではありません。
同じテーマで、<script> と <style> の両方が有害と見なされます。これらの要素(およびそのコンテンツ)はデフォルトではレンダリングされず、明示的に p.AllowUnsafe(true) を設定する必要があります。これらの要素を許可すると、JavaScript(および明白に記述された XSS)と CSS(DOM を変更して JS を挿入できる)のいずれかを明示的に許可することになるため、HTML サニタイザーを使用する目的が無効になることに注意してください。さらに、このライブラリの制限により、HTML が有効に構造化されているかどうかを認識しないため、WhatWG HTML パーサー標準 に組み込まれている安全メカニズムの一部をこれらの要素がバイパスできる可能性があります。
不正な HTML を修正するのは bluemonday の仕事ではありません。悪意のある HTML が通過するのを防ぐのが bluemonday の仕事です。HTML 要素の不一致や、要素の不適合なネストがある場合、それらは残ります。ただし、適切に構造化された HTML であれば、bluemonday はそれを壊しません。
bluemonday.UGCPolicy() などの既存のポリシー(探しているものの 90% をカプセル化しているが、必要以上に多くのことを行う)を取得し、不要な余分なものを削除して 100% 望み通りのものにすることができるtable 要素が caption の子孫ではない、colgroup、thead、tbody、tfoot、tr が許可される、文字データが許可されない)