Skip to main content

ユーザーをクロスサイトリクエストフォージェリから守る

10月

20, 2020

by Iron_Legion


テック

こんな筋書きをイメージしてください。自分の銀行口座のアカウントにログインして残高を確認した後、他のウェブサイトに行きました。 突然、アカウントがハイジャックされたことに気付きます! 資金が転送され、サイバー攻撃者があなたのパスワードをリセットしようとします。 あなたは、セキュリティに気をつけていて、フィッシング詐欺に引っかかったり、パスワードをシェアしたりすることは絶対にありません。では、なぜこのようなことが起きたのでしょうか?

これは、クロスサイトリクエストフォージェリの一例で、よくCSRF(またはeXtremeな気分ならXSRF)と略されています。 XSRFは、ありふれたウェブセキュリティのエクスプロイトで、過去に大手テクノロジー企業のGoogle、Netflix、マイクロソフトさえもが、これに対して脆弱であることが判明しています。 幸いなことに、RobloxはXSRFに対する安全対策をしていますが、当社のウェブサイトを保護するためにこれをどのように機能させて、どんなツールを使って効果を出しているのか掘り下げていきましょう。

サイバー攻撃者のウェブサイトを訪問すると、そのウェブサイトがあなたのブラウザにあなたの代わりにリクエストを送信させます。 あなたのデータを操作しようとするか、パスワードの再設定やメールアドレスの変更、またはバーチャルアイテムの購入など、あなたのブラウザに不本意な操作を実行させます。 あなたのブラウザには、リクエストと一緒にクッキーとセッション情報が含まれるため、ウェブサーバーはリクエストを信頼し、リクエストされたことを実行します。

これは、インターネットの初期から存在している欠陥です。 数多くの解決法は、リファラーか元のヘッダーの認証にからんで開発されてきましたが、すべての場合において機能するわけではありません。 SameSite cookiesは、多くの場合で助けとなりますが、まだすべてのブラウザでサポートされておらず、もう一つのセキュリティの層が必要です。

この業界における標準的な解決法は、すべてのAJAXリクエストかウェブサイトから送信されるフォーム投稿に「XSRFトークン」を添付することです。 XSRFトークンは、各ユーザーに固有の秘密の数値で、これは概してページソースに埋め込まれており、 あなたのドメイン上で実行中のJavascriptが取得できるようにするものですが、第三者ウェブサイトからは取得できません。

HTTPリクエストがバックエンドに到達するとき、 ウェブサーバーはXSRFトークンが予測される数値と合致するかを確認します。 しない場合、そのリクエストは拒否され、エンドポイント(ネットワークに接続されている端末)は適切なエラー応答を返します。 この数値は、各ユーザーに固有のもので、リプレイ攻撃のリスクを軽減させるために時間が経つにつれて変更されるものであることに注意してください。

RobloxにおけるXSRF

Robloxでは、歴史的にXSRF保護はオプトイン承諾済みの場合に適用してきましたが、これが意味することは、各データ変異をするエンドポイントに以下のようなC# 属性のようなもので手動でタグ付けしなければなりませんでした。 [XsrfProtection]. これは、Robloxにとって何年も実行可能な解決方法でしたが、これはエンジニアが各エンドポイントに特定のコードを追加することを忘れないことに依存しているため、これは根本的にデフォルトで不安定で効率の悪いものでした。 当社は、その不在にフラグを付けるコード解析ツールを追加しました。

当社が2015年に新しいRESTfulウェブAPIの枠組みに取り組み始めたとき、特定のエンドポイントにはオプトアウト(拒否)できるオプションをつけてデフォルトでXSRF保護を追加しました。 これはそつなく機能しましたが、それでも既存のエンドポイントにこの保護を追加するという問題に取り組む必要がありました。

当社の目標:

  • Find a way to introduce 既存のすべてのエンドポイントで全面的にXSRF保護を導入する方法を見つけること。
  • 解決法は、各エンドポイントでのカスタマイズを必要としないこと。
  • 解決方法は、製品ユーザーに与える影響が最小限かほぼゼロでなければならない。

当社にとっての挑戦は、当社のすべてのフロントエンドの枠組みに対応する必要があることです。

  • React (HTTPコールのためにAxiosと一緒に使用されるメインの枠組み)
  • AngularJS
  • jQuery
  • ASP.Net MVC
  • ASP.Net WebForms
  • ネイティブな C++ コード(RobloxゲームクライアントからのHTTPコールに使用される)

ここがちょっと厄介になってくるところです。 ウェブサイト上の当社のレガシーページの中には、ブラウザがリクエストをすることを担当しており、通常のフォームブラウザ投稿に依存するものがあります。 ブラウザ投稿は、添付ヘッダーをサポートせず、古いトークンを通す失敗済みのリクエストの再試行もサポートしません。

ASP.Net WebFormsを使用しているページには、投稿リクエスト全体のペイロードは、ViewStateを使用して認証されるため、XSRFを防ぐために各ユーザーに固有にするためにViewStateUserKeyをカスタマイズするだけです。

ASP.Net MVC エンドポイントでは、2つの異なる使用例を気にする必要があります。

  • AJAX リクエスト
  • ブラウザフォーム投稿

どのMVCエンドポイントもどちらの方法でもアクセスでき、これは当社のバックエンド認証をヘッダーでもフォーム本体でもXSRFトークンを探すように適応させなければならないという意味です。

ASP.Netのブラウザフォーム投稿用の作りつけのアプローチは、 [ValidateAntiForgeryToken] 属性を使うことですが、各エンドポイントに手動で追加しなければなりません。 さらに、フォージェリ(偽造)防止トークンを埋め込むページ上でリクエストが起きているのでない限り、それは AJAXリクエストをサポートしません。

パート1: ヘッダーとしてのXSRF自動添付

当社のコアJavascriptライブラリでは、送信される前に各リクエストを改変するハンドラを登録し、リクエストがPOST/PATCH/PUT/DELETEなら、XSRFトークンをヘッダーとして添付します。 もし、リクエストが403 「トークン認証失敗」のステータスがついて返らないと、コードは自動的に再試行するように設定してあります。 この抽象化によって、javascriptクライアントコードが XSRFの存在をまったく気づかなくしてしまいます。

Javascriptの枠組みの基づいたこれらの3つの異なる方法を登録しなければまりませんが、最終的な結果はほぼ同じです。

  • React – axios.interceptors.request.use
  • AngularJS – httpProvider.interceptors.push
  • jQuery – $.ajaxPrefilter

パート2: フォーム投稿におけるXSRFトークンの自動注入

当社は、すべての場合に機能し、個別のページに対して特別なカスタマイズを要さない一般的な解決方法を求めていたので、すべてのブラウザフォーム送信を傍受するJavascriptを書くことに決めました。

これは、HTMLFormElement.prototype.submitの動作をオーバーライドして、送信する前にフォームに <input name=”CsrfToken” value=”secret”> を追加する機能をつけることで可能になります。

ユーザーがページを数分、待機状態にしたままにして、XSRFトークンの期限がすでに切れた後にフォームを送信しようとする場合はどうでしょう? AJAXリクエストにとっては、これは大した問題ではなく、単にリクエストを再試行できます。 しかし、フォーム投稿では、ブラウザがページフローをコントロールしていて、私たちはこの筋書きを優雅に処理できません。 当社は、クライアントに求められたときにリスポンス・ヘッダーでXSRFトークンを返したエンドポイントを露出することでこれを解決しました。 もし、ユーザーがフォームを送信しようとしたときに、トークンが古くなっていることを検知したとき、元のページ読み込み時間に基づき、フォーム送信をキャンセルし、有効なものが確実に得られるように最新のXSRFトークンを求めます。 その最新のトークンを得たときのみ、フォームの再送信へと進みます。

当社のバックエンド導入のために、当社のすべてのウェブサイトのために基本レベルでXsrfValidationFilterAttribute 動作フィルタ属性を登録しました。 このクラスは、各エンドポイントが施行される前に実行され、and verifies that XSRFトークンがすべてのデータ変換エンドポイント(HTTP方式がPOST、PUT、PATCHまたはDELETE)にあるかを確認します。

学んだこと

新しいセキュリティ機能を追加するとき、実施し始める前にその影響を測ります。

各エンドポイントに、どのURLがXSRFに優雅に対処しなかったかを教えてくれるメトリックを追加することは、デバッギング問題の場合、貴重であると判明しました。 当社は、Content-Security-Policyを有効化する前に同じアプローチを使いました。

Javascript属性のインデックス化にはご注意を!

HTML要素に表示名属性がついた入力が含まれる場合、それは同じ表示名のついたいかなる属性にも優先されます。

<form action=”endpoint-goes-here”>

<input name=”action” value=””>

</form>

当社のウェブサイト上のページの一つにフォーム入力名が ‘action’ というのがあったので、 当社の form.action と呼ばれたXSRFコード は、フォーム属性の代わりに偶発的に入力値を読み込んでいました!

ありがたいことに、当社の品質保証テスターが早期に問題を発見し、 form.getAttribute(“action”) に変更することで問題を解決しました。


Robloxコーポレーションとこのブログは、いかなる企業もサービスをも、推奨も支持もしません。 また、このブログに含まれる情報の正確さや完全性について、いかなる保証または約束もしません。

このブログ記事は、元はRobloxテックブログ に掲載されたものです。