クロスサイトリクエストフォージェリ(CSRF)は、エンドユーザーにウェブアプリケーションのバックエンドで望ましくないアクションを実行させる攻撃の一種です。ユーザーが現在認証済みの状態です。言い換えれば、保護されていない場合、Google Chromeなどのブラウザに保存されたCookieを使用して、ユーザーが現在Chase.comにアクセスしているかHorrible-Hacker-Site.comにアクセスしているかに関わらず、ユーザーのコンピュータからChase.comにリクエストを送信できます。
CSRFトークンは、限定版のグッズのようなものです。セッションはサーバーにユーザーが「自分が言っている人物である」ことを伝えるのに対し、CSRFトークンはサーバーにユーザーが「自分が言っている場所にいた」ことを伝えます。SailsアプリでCSRF保護が有効になっている場合、サーバーへのすべての非GETリクエストには、'_csrf'パラメータまたは'X-CSRF-Token'ヘッダーのいずれかとして含めることができる特別な「CSRFトークン」を添付する必要があります。
トークンを使用すると、Sailsアプリをクロスサイトリクエストフォージェリ(またはCSRF)攻撃から保護できます。攻撃者は、ユーザーのセッションCookieだけでなく、このタイムスタンプ付きの秘密のCSRFトークンも必要になります。このトークンは、ユーザーがアプリのドメイン上のURLにアクセスすると更新/付与されます。これにより、ユーザーのリクエストがハイジャックされていないこと、そしてリクエストが意図的で正当なものであることを確信できます。
CSRF保護を有効にするには、フロントエンドアプリでトークンを管理する必要があります。従来のフォーム送信では、<form>
にCSRFトークンを非表示の入力として送付することで簡単に実現できます。あるいはもっと良い方法として、AJAXリクエストを送信する際に、CSRFトークンをリクエストパラメータまたはヘッダーとして含めることができます。そのためには、security/grant-csrf-token
をマウントしたルートにリクエストを送信してトークンを取得するか、さらに良い方法として、exposeLocalsToBrowser
パーシャルを使用してビューローカルからトークンを取得します。
いくつかの例を以下に示します。
exposeLocalsToBrowser
パーシャルを使用して、クライアント側のJavaScriptからトークンにアクセスできるようにします。例:
<%- exposeLocalsToBrowser() %>
<script>
$.post({
foo: 'bar',
_csrf: window.SAILS_LOCALS._csrf
})
</script>
security/grant-csrf-token
をマウントしたルートにGETリクエストを送信してトークンを取得します。JSONで応答します。例:
{ _csrf: 'ajg4JD(JGdajhLJALHDa' }
トークンをHTMLの非表示のフォーム入力要素に直接レンダリングします。例:
<form>
<input type="hidden" name="_csrf" value="<%= _csrf %>" />
</form>
Sailsは、すぐに使用できるオプションのCSRF保護をバンドルしています。組み込みの強制を有効にするには、sails.config.security.csrf(通常はプロジェクトのconfig/security.js
ファイルにあります)を次のように調整するだけです。
csrf: true
プロジェクトのconfig/routes.js
ファイルの任意のルートにcsrf: true
またはcsrf: false
を追加することで、ルートごとにCSRF保護のオン/オフを切り替えることもできます。
POST、PUT、またはDELETEリクエストを介してSailsバックエンドと通信する既存のコードがある場合は、CSRFトークンを取得し、それらのリクエストのパラメータまたはヘッダーとして含める必要があることに注意してください。その詳細については、すぐに説明します。
ほとんどのNodeアプリケーションと同様に、SailsとExpressは、ConnectのCSRF保護ミドルウェアと互換性があり、このような攻撃から守ることができます。このミドルウェアはSynchronizer Token Patternを実装しています。CSRF保護が有効になっている場合、Sailsサーバーへのすべての非GETリクエストには、ヘッダーまたはクエリ文字列またはHTTPボディのパラメータによって識別される特別なトークンを添付する必要があります。
CSRFトークンは一時的でセッション固有です。例えば、メアリーとムハンマドがSails上で実行されているECサイトにアクセスしている買い物客で、CSRF保護が有効になっているとします。月曜日にメアリーとムハンマドが両方とも購入を行うとします。そのためには、サイトは少なくとも2つの異なるCSRFトークン(メアリー用とムハンマド用)を発行する必要があります。その後、ウェブバックエンドがトークンが不足しているか間違っているリクエストを受け取ると、そのリクエストは拒否されます。これで、メアリーがオンラインポーカーをするために移動した場合でも、サードパーティのウェブサイトが彼女のCookieを使用してサイトに悪意のあるリクエストを送信するようにブラウザを騙すことができないと確信できます。
CSRFトークンを取得するには、localsを使用してビューでブートストラップするか(従来の複数ページのウェブアプリケーションに適しています)、特別な保護されたJSONエンドポイントからAJAXを使用して取得します(シングルページアプリケーション(SPA)に便利です)。
旧式のフォーム送信の場合、ビューからフォームアクションにデータを渡すのと同じくらい簡単です。ビューローカルとしてアクセスできるビューでトークンを取得できます:<%= _csrf %>
例:
<form action="/signup" method="POST">
<input type="text" name="emailaddress"/>
<input type='hidden' name='_csrf' value='<%= _csrf %>'>
<input type='submit'>
</form>
フォームでmultipart/form-data
アップロードを行う場合は、file
入力の前に_csrf
フィールドを配置してください。そうでないと、ファイルのアップロードが完了する前にタイムアウトと403エラーが発生する可能性があります。
AJAX/Socketを多用するアプリでは、ページにブートストラップするのではなく、動的にCSRFトークンを取得することをお勧めします。そのためには、config/routes.js
ファイルで、security/grant-csrf-token
アクションを指すルートを設定します。
{
'GET /csrfToken': { action: 'security/grant-csrf-token' }
}
次に、定義したルートにGETリクエストを送信すると、JSONとしてCSRFトークンが返されます。例:
{
_csrf: 'ajg4JD(JGdajhLJALHDa'
}
セキュリティ上の理由から、ソケットリクエストを介してCSRFトークンを取得することはできません。ただし、ソケットリクエストを介してCSRFトークンを「消費」することはできます(下記参照)。
security/grant-csrf-token
アクションは、クロスオリジンリクエストで使用することを意図していません。一部のブラウザでは、デフォルトでサードパーティのCookieがブロックされるためです。クロスオリジンリクエストの詳細については、CORSドキュメントを参照してください。
CSRF保護を有効にすると、Sailsアプリに行われたPOST、PUT、またはDELETEリクエスト(仮想リクエストを含む、例:Socket.ioからのリクエスト)には、ヘッダーまたはパラメータとしてCSRFトークンを添付する必要があります。そうでなければ、403(Forbidden)レスポンスで拒否されます。
例えば、jQueryを使用してウェブページからAJAXリクエストを送信する場合
$.post('/checkout', {
order: '8abfe13491afe',
electronicReceiptOK: true,
_csrf: 'USER_CSRF_TOKEN'
}, function andThen(){ ... });
一部のクライアントサイドモジュールでは、AJAXリクエスト自体にアクセスできない場合があります。この場合、クエリURLにCSRFトークンを直接送信することを検討できます。ただし、そのようにする場合は、トークンを消費する前にURLエンコードすることを忘れないでください。
..., {
checkoutAction: '/checkout?_csrf='+encodeURIComponent('USER_CSRF_TOKEN')
}
_csrf
パラメータの代わりにX-CSRF-Token
ヘッダーとしてCSRFトークンを送信することもできます。- ほとんどの開発者と組織にとって、CSRF攻撃は、ユーザーがブラウザから(つまり、HTML/CSS/JavaScriptフロントエンドコードから)Sailsバックエンドにログイン/安全にアクセスできるようにする場合にのみ懸念事項となります。そうではない場合(例えば、ユーザーがネイティブのiOSまたはAndroidアプリからのみセキュアなセクションにアクセスする場合)、CSRF保護を有効にする必要がない可能性があります。なぜなら、このページで説明されている一般的なCSRF攻撃は、ユーザーが同じクライアントアプリケーション(例:Chrome)を使用して異なるウェブサービス(例:Chase.com、Horrible-Hacker-Site.com)にアクセスする場合にのみ可能だからです。
- CSRFの詳細については、Wikipediaを参照してください。
- 従来のフォーム送信でCSRFトークンを「消費」するには、上記の例(「ビューローカルの使用」の下)を参照してください。