STORES Tech Blog

こだわりを持ったお商売を支えるプラットフォーム「STORES」の開発チームによる技術ブログです。

こだわりを持ったお商売を支えるプラットフォーム「STORES」の開発チームによる技術ブログです。

CSPを利用してRailsアプリから安全にiframeを制限する

STORES EC本部のソフトウェアエンジニア @_morihirok です。

STORES ECではクリックジャッキングの脆弱性に対応するため、2021年の2月に他ドメインのサイトからのiframe要素などによる読み込みを制限するアップデートを行いました。

コンテンツセキュリティポリシー(以下CSP)を利用し安全にアップデートを行うことができましたので、今回はその取り組みについてご紹介させていただければと思います。

今回のアップデートに至るまでの背景

STORES ECのバックエンドはRuby on Railsで開発されています。Railsではデフォルトの設定で X-Frame-OptionsSAMEORIGIN が指定されているため、「そもそも何もしなくてもクリックジャッキングの脆弱性には対応されているのでは?」と思われる方も多いと思います。

これには歴史的経緯がありまして、

  • STORES ECはSTORES ECのサブドメインの他にストアオーナーさんが希望したドメインでもストアページ(ストアオーナーさんが作成し公開したECサイト)を表示させられる機能がある
  • STORES BUTTONという任意のオリジンにSTORES ECの購入ボタンが埋め込める機能がある
    • iframeにて動作を実現している

といったように開発の初期からiframeを活用しておりました。その中でSTORES ECのRailsリポジトリのconfigには以下のような歴史的一文が付与されました。

config.action_dispatch.default_headers.delete('X-Frame-Options')

時は2020年に戻りまして、STORESの開発チームにセキュリティチームが発足し、Webアプリケーションのセキュリティリスクが調査され、適切に評価されるようになりました。 その中で対応優先度が高く設定されたのが今回の「クリックジャッキングの脆弱性」でした。

しかし、長年 X-Frame-Options を付与せずに運用してきたアプリケーションです。すでにiframeに埋め込まれているストアページもいくつか観測できており、いきなりiframeを禁止するのはストアオーナーさんの不利益にも繋がります。どのように対応するか工夫が必要でした。

これを解決するためにコンテンツセキュリティポリシーを利用することにしました。

コンテンツセキュリティポリシー(CSP)を利用してiframeを制限する

CSPについては Webブラウザセキュリティ ― Webアプリケーションの安全性を支える仕組みを整理する にて非常にわかりやすくかつ体系的にまとめられているのでご一読をおすすめします。

Webブラウザセキュリティ ― Webアプリケーションの安全性を支える仕組みを整理するwww.lambdanote.com

ここではMDNの冒頭の一文を引用させていただき、CSPについての詳しい説明は省略します。

コンテンツセキュリティポリシー (CSP) は、クロスサイトスクリプティング (XSS) やデータインジェクション攻撃などのような、特定の種類の攻撃を検知し、影響を軽減するために追加できるセキュリティレイヤーです。

developer.mozilla.org

今回実施したいのはiframeなどからページを埋め込むことのできる親を制限することです。 これは frame-ancestors というディレクティブを使用することで実現できます。

レスポンスヘッダに以下のようにCSPヘッダを指定すると、ブラウザ側で指定された親からの参照であるかを検証してくれます。'self'を指定することによって同一オリジンからの埋め込みのみに制限できます。

Content-Security-Policy: frame-ancestors 'self';

Railsではバージョン5.2からCSPヘッダを付与するための機能が提供されており、上記のヘッダは config/initializers/content_security_policy.rb で以下のように設定することができます。

Rails.application.config.content_security_policy do |policy|
  policy.frame_ancestors :self
end

また、STORES ECではいくつかの機能でiframeを利用しています。該当エンドポイントのコントローラのアクションに以下のような設定をすることで、CSPヘッダの付与を回避することが可能です。

content_security_policy false, only: :show

余談ですが、CSPの frame-ancestors ディレクティブは X-Frame-Options を再設計した仕組みとして提供されており、X-Frame-Options が抱えていた微妙な問題の解決を行なっています。(詳細は Webブラウザセキュリティ ― Webアプリケーションの安全性を支える仕組みを整理する をご一読いただくのをおすすめします。)

CSPをreport-onlyモードで動作させる

上記の設定を入れると、すでにiframeに埋め込まれているストアページは表示されなくなってしまいます。

いきなりこのアップデートを行うのはストアオーナーさんにとって不利益につながるため、まずは report-only モードを利用して制限を違反しているページをロギングし影響範囲を調査することにしました。

report-only モードを利用するには Content-Security-Policy ヘッダの代わりに Content-Security-Policy-Report-Only ヘッダを利用します。

Content-Security-Policy-Report-Only は指定されたポリシーに違反するか検証だけ行い制限はしません。ポリシーには Content-Security-Policy と同様のものを指定します。 これに report-uri ディレクティブを設定することで、ポリシー違反を指定したURIに報告してくれます。

以下のようなヘッダを設定することで、ブラウザによってiframeへの埋め込み制限を違反したページを指定したURIに報告してくれるようになります。

Content-Security-Policy-Report-Only: frame-ancestors 'self'; report-uri http://example.com/csp-violation-report-endpoint;

Railsも report-only モードに対応していて、上記のヘッダを設定するためにはCSPヘッダのときと同様に config/initializers/content_security_policy.rb に設定を行います。

Rails.application.config.content_security_policy do |policy|
  policy.frame_ancestors :self

  policy.report_uri '/csp-violation-report-endpoint'
end

Rails.application.config.content_security_policy_report_only = true

policy.report_uri にて違反レポートをPOSTするURIを指定し、content_security_policy_report_onlytrue にすることで report-only モードで運用する、つまり Content-Security-Policy-Report-Only を付与する、という設定になります。

今回、違反レポートのリクエストはSTORES本体と同じRailsアプリで受けるようにしました。

違反レポートは POST で送られてくるので対応するルーティングとアクションを記述します。注意点としてはRailsでは、リクエストの「Content-Type」に「application/json」が指定されていれば、パラメータが自動的にparamsハッシュに読み込まれますが、違反レポートは Content-Type application/csp-report としてリクエストされるため params ハッシュに読み込まれません。いつもの感覚で params ハッシュを見に行くとハマってしまうのでご注意ください。

こうして取得した違反レポートをログに書き込んでいきました。

他にはAPI Gateway + AWS Lambdaみたいな構成でロギング用のエンドポイントを立てるのもアリかなと思いますし、エラートラッキングサービスに流すのも良いと思います。例えばSTORESではエラートラッキングサービスとしてSentryを利用しており、SentryではCSP違反レポートを受け取るための設定を提供しています。

docs.sentry.io

ログから自信を持ってiframeを制限する

report-only モードにて運用を始めて数日たち、いくつかのストアページがiframe内に埋め込まれていることがわかりました。 カスタマーズ部門と連携し、これらのストアページを持つストアオーナーさんに対して個別に、セキュリティ対策でiframeを制限する旨をお伝えし了承していただきました。

ストアオーナーさんにとっては突然のお願いとなってしまったため、状況を都度確認しながら制限のアップデートを行う日を調整していきました。そして、2021年2月にiframe内への埋め込みを制限するアップデートをリリースしました。このリリースのときの差分は以下だったため、安心してリリースすることができました。

- Rails.application.config.content_security_policy_report_only = true
+ # Rails.application.config.content_security_policy_report_only = true

また、このリリースの後に晴れて歴史的一文

config.action_dispatch.default_headers.delete('X-Frame-Options')

の削除にも成功しました。

おわりに

STORESでは開発チームの近い場所に専門知識を持ったセキュリティチームが存在しています。セキュリティチームの適切なアドバイスによって、通常のプロダクト開発だけでなく今回のようなセキュリティ関連の作業にも継続して取り組むことができます。また、設計段階からセキュリティ関連の作業を組み込むことができるので、強固なセキュリティの維持と開発速度の向上を実現できます。

強固なセキュリティを維持することは、ストアオーナーさんの安心安全なお商売を支える一助になります。セキュリティレベルを上げることは大きな価値であるという環境でのエンジニアリングに興味がある方はぜひ気軽にご連絡ください!

hello.hey.jp