(この記事は KDDI Engineer & Designer Advent Calendar 2021 の第22日目エントリーです)
実現したいこと/導入のきっかけ
運営しているWebサイト(KDDI Open API for Developers)のセキュリティ強化をしたい
色々と試していく中でCPSなるものに出会ったので導入してみる
そもそもCSPとは
簡単にまとめると
・セキュリティを強化する(主にXSS対策) HTTPレスポンスヘッダー
・画像やスクリプトなどを扱う際、信頼したソースからのものかどうかチェックする
詳しくは参考記事を読む方が早いです
概要:https://developer.mozilla.org/ja/docs/Web/HTTP/CSP
ドキュメント:https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Security-Policy
この辺読んだらより理解できた
https://techblog.securesky-tech.com/entry/2020/05/21/
https://blog.jxck.io/entries/2017-02-13/csp-report-case-study.html
自身のサイトに実現可能か?
CSPはブラウザ上に公開するコンテンツに対してホワイトリストで表示・ブロックを管理します
サイトの構成によってはCSPの導入・管理が難しくなる場合や予期せぬエラーを引き起こしてしまう場合があります
そのため自身のサイトの構成に対してCSPが導入可能か、もしくは導入・管理コストは適切かを調べる必要があります
例:画像
サイト上で表示する画像や、サイト上からDLさせるファイルについてもCSPの管理対象です
特に外部ドメインから参照する場合には、そのドメインごとにポリシーの設定が必要です。
サイトのコンテンツ追加に合わせて参照するドメインの数が増えるような構成の場合、ポリシーの管理は段々と煩雑になっていくでしょう
例:インラインスクリプト
HTML上でインラインのスクリプトを記述している場合、基本的には全てCSPによってブロックされてしまいます
// 例えば以下のような記述
<script>some script</script>
<button onclick="alert('hoge');">ボタン</button>
回避方法はあるものの、多用しないが原則になります
やったこと
導入に先立った調査
自サイトにCSPを導入するにあたり、どの程度既存ソースに改修が必要か、
また、決めなければいけない運用ルールなどがないかを調査しました
この時点での基本方針は以下
- 必要になる権限を最小限に設定する
→default-srcは指定しない - unsafe- な指定は避ける(特にinline)
この時点で気になったことは以下の3点でした
インラインスクリプトを使っている
GoogleAnalytics関連の埋め込みコードのみインラインで記述されている
- 性質上一度設定すればコード修正はない ためこのスクリプトのみ許可する方針
スクリプトのhash値をポリシーで管理することで決定
外部サイトで公開済みの画像を使用したい
お知らせページで公開する記事中には、他の自社サイトと連携した記事も掲載予定
その際に他サイトのバナーや記事中の画像などをお知らせの中に表示させたいとのこと
- 外部ドメインの画像やファイルはバックエンドAPIを通じてのみ取得する使用に変更
- ポリシーにはAPIのエンドポイントのみ追加
結果お知らせで外部サイトの画像を使う度にポリシーを更新する
という苦行を回避
ポリシーの妥当性チェックの難しそう
ポリシー指定に不足がある場合はそのリソースの参照を実際に行うまでエラーが発生しない
(画像の表示やAPIコールなど)
そのため厳密にポリシーの妥当性を確認しようとすると
ポリシーを設定・更新する際に全てのページにおいて画像の表示やスクリプトの実行(ボタン押下や画面遷移など)
を行いポリシー違反がないかチェックしないといけない
流石に人の行うべき作業ではないし、テストの自動化もかなりコストが高くなりそう
というわけでポリシーの妥当性チェックについては以下の方針で運用してみることに
2. ポリシー違反結果(CSPReport)を1ヶ月収集する
3. 1ヶ月後のレポート結果を見てポリシーの妥当性を判断する
つまり実ユーザにサイトを触ってもらっている中で1ヶ月レポートが上がらなければ
妥当なポリシーとしてCSPとして設定しても良いだろうという考えです
とりあえずReportOnlyで試してみる
WebサイトはAWS Amplifyを用いて構築しているので
CSPのポリシーはAmplifyのカスタムヘッダに記述することに
最終的に設定したヘッダは以下のように(一部省略)
customHeaders:
- pattern: '**/*'
headers:
- key: Content-Security-Policy-Report-Only
value: >-
script-src 'self' 'report-sample'
'sha256-h~~~~~~0='
'sha256-Q~~~~~~Y='
https://ssl.google-analytics.com/ https://www.googletagmanager.com;
style-src 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: {backend apiのエンドポイント};
font-src https://fonts.gstatic.com;
connect-src https://www.google-analytics.com;
object-src 'none';
report-uri https://{レポート収集用API-GWのエンドポイント};
前述の通りサイトではgoogle analyticsを使用しているのでGA用のポリシー設定が多いです
script-srcのhash(sha256-~~)2つもGA用のインラインスクリプトを許可するための設定です
そして最後にreport-uriとしてレポート送信先を設定
この状態でサイトからで上記ポリシー外のリソースを表示しようとしたり、スクリプトの実行をしようとすると
CSPが検知しレポートを送信します
(今はContent-Security-Policy-Report-Onlyなのでユーザ影響は無い)
このレポートをAPI-GWで受けてCloudWatchにログとして残す仕組みを構築しました
CloudWatchにしたのは集計のしやすさを考慮しての選択です
本番環境にデプロイして1ヶ月レポートを収集してみます
1ヶ月Reportを収集してみて
CSPの設定を本番環境に乗せたのが11月上旬で、先日1ヶ月の収集が終わったので
集計してみることに
この時主にみるのはレポート内の3種
csp-report.blocked-uri
どのURIへのアクセスがブロックされたのか
csp-report.violated-directive
どのディレクティブのポリシーに違反したのか
csp-report.document-uri
どのページ上で起きたのか
transrateの話
数件ではあるがでhttps://translate.google.com~~
のポリシー違反が報告されていた
URLを見るに拡張機能などを使用してGoogle翻訳にページを食わせるとポリシー違反が起きてしまう様子
母国語が日本語以外のユーザの訪問を想定するならポリシーに追加しておいた方がユーザフレンドリーなサイトと言える
chrome拡張のブロック
その他script-src-elemでも多くのレポートが上がっていた
blocked-uriやdocument-uriもまちまちで、恐らくchrome拡張などのブラウザ拡張によって実行されたスクリプトも含まれている
ただ、どこまでが拡張機能でどこからが悪意のあるスクリプトなのかの境界が分からないことから
一旦は全てブロックしておこうと判断
まとめと今後について
冗長になってしまいましたが以上が
CSPの導入を検討して、試しに本番環境に乗せて様子を見た話になります
机上の検討ではどうしても限界があったので、それならReportOnlyで試してみよう
という流れは結果うまく働いたと思っています
google翻訳の件は机上では絶対に出てこなかった
今後の運用方針
- 今回設定したCSPレポートにgoogle翻訳系のポリシーを追加して本リリース(非Report-Only)
- 今後ポリシーを追加する場合は以下に従う
- ポリシーを広げる場合は即時追加
- ポリシーを狭める場合は一度Report-Onlyに追加して影響調査を行う(問題がなければポリシーに追加)
- ポリシー設定で迷ったら一旦広めに設定し、その後狭める
宣伝
そんなCSPを導入してセキュリティ強化を目指しているサイトKDDI Open API for Developers では
KDDIが提供する魅力的なプロダクトを紹介しています
気になるものがあればサイト内フォームから気軽にお問い合わせください
プロダクトについても随時追加予定です