In this article
シンプルだからといって安全とは限らない:GolangでこのXSSパターンを避ける
クロスサイトスクリプティング(XSS)攻撃は、他のユーザーが閲覧するWebページに攻撃者が悪意のあるスクリプトを埋め込める、一般的なWeb脆弱性です。シンプルにすれば、コードもより安全になると思うかもしれません。サードパーティの依存関係を使わず、抽象化も減らす。一見理想的ですが、本当にそうでしょうか?
この記事では、シンプルなWebサーバーであるGolangプログラムを例に、シンプルだからといって常に安全とは限らない理由を解説します。
クロスサイトスクリプティング攻撃について
クロスサイトスクリプティング(XSS)とは、ユーザーデータの窃取やセッションの乗っ取り、Webサイトの改ざんなどに使われるスクリプトを指します。Golangには堅牢なWeb開発ツールが備わっていますが、適切な対策を講じなければ、XSSの脆弱性を免れることはできません。
Golangにおけるクロスサイトスクリプティングの例
XSS脆弱性のよくある原因は、ユーザー入力を適切にサニタイズしたり、その他のセキュリティ対策を講じたりせずに、HTML出力へ直接埋め込むことです。なお、効果的なセキュリティ対策は必ずしもサニタイズとは限らず、出力エンコーディングが適切な場合もあります。
Goアプリケーションの例として、フォームフィールドやURLパラメーター(クエリー文字列など)を介してユーザーからデータを受け取り、そのデータをHTTPレスポンスの一部としてHTMLページに直接挿入するものがあります。攻撃者は悪意のあるJavaScriptコードを注入できます(これが「クロスサイトスクリプティング」という名前の由来です)。別のユーザーがそのページを表示すると、ブラウザーが注入されたスクリプトを実行し、データやセッションが侵害されるおそれがあります。
GolangのHTTPサーバーでよくある間違いは、fmt.Fprintなどの関数を使い、ユーザーが提供したデータをHTTPレスポンスに直接書き込むことです。次のコード例を見てみましょう。
http.HandleFunc("/greet", func(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
response := fmt.Sprintf("<html><body><h1>Hello, %s!</h1></body></html>", name)
fmt.Fprint(w, response)
})ユーザーがnameクエリー文字列に<script>alert('XSS!')</script>のような値を設定すると、このコードはJavaScriptのalert()コードをページに注入し、基本的なXSS攻撃を実行します。fmt.Sprintf関数はHTMLエンティティを自動的にエスケープしないため、アプリケーションは脆弱な状態になります。
このGolangのコード例は、オープンソースのセキュリティチャレンジの一部です。Snykはこのコードリポジトリを取り込み、リクエストURLからサニタイズされていない入力を検出するとともに、その入力がHTMLテンプレート文字列をレンダリングする安全でないfmt.Fprint関数の呼び出しに渡される流れを把握しました。SnykのSASTによるソースコードとコードパスの詳細な分析結果をご覧ください。

GolangでXSSを修正する方法
上記のGolangの脆弱な例は、ユーザー入力をHTMLレスポンスに含める前に、適切にエスケープまたはサニタイズする必要があることを示しています。
危険性のあるHTMLコードをエスケープするには、アプリケーションのコード、または利用するサードパーティ製の依存関係によって、特定のユーザー入力をエンコードし、HTML DOM要素としてそのまま解釈されないようにする必要があります。たとえば、ReactやVueなどのフロントエンドフレームワークは、文字<を対応するHTMLエンティティ<に変換します。
では、サードパーティ製の依存関係に常に頼るべきなのでしょうか?答えはノーです。しかし、依存関係を継続的に監視・スキャンしてセキュリティ脆弱性を検出し、自動更新によって適切に保守すれば、セキュリティリスクを管理しやすくなります。また、ライブラリを誤った方法で使うことで、脆弱性を招くおそれがある点にも注意が必要です。
Golangのセキュアなコードに関する問題には、どのような対策があるでしょうか?Goのコード、依存関係、パッケージマネージャーに関して、SnykはGoを幅広くサポートしています。