CSRFを理解して、適切な対策できていますか? <CSRF攻撃の概要と防御方法について紹介>
CSRFを理解する
CSRF(Cross-Site Request Forgery)とは、Webアプリケーションの脆弱性の一つで、攻撃者が被害者の代わりに意図しない操作を行わせることができる攻撃手法です。
言い換えると攻撃者の罠によって、Webアプリの元々持っているユーザーの意思ではなく呼び出されてしまうということです。
例を見ていきましょう。
あるECサイトで、ユーザーがカートに商品を追加する際に使われるフォームを想定してみます。
ECサイトのドメインはhttps:://example.com
<form action="<https://example.com/cart/add>" method="POST">
<input type="hidden" name="item_id" value="1234">
<input type="hidden" name="quantity" value="1">
<button type="submit">Add to Cart</button>
</form>
攻撃者は、自分が作成した罠ページに被害者を誘導し、そのページに以下のようなフォームを設置します。
攻撃者サイトのドメインはhttps://evil.examle.com
<form action="<https://example.com/cart/add>" method="POST">
<input type="hidden" name="item_id" value="5678">
<input type="hidden" name="quantity" value="999">
<button type="submit">Add to Cart</button>
</form>
この罠ページにアクセスした被害者は、自動的にフォームを送信してしまい、意図しない商品がカートに追加されてしまいます。
ちなみに混同されやすいCSRFとXSSについての違いをまと
- CSRF:Webアプリケーション利用者自身が意図しない処理が実行されてしまう脆弱性または攻撃手法
- 攻撃がアプリサーバ内で実行
- アプリで定義された処理で攻撃
- 攻撃がアプリサーバ内で実行
- XSS:ユーザーがWebページにアクセスすることで不正なスクリプトが実行されてしまう脆弱性または攻撃手法
- 攻撃がブラウザで実行
- 攻撃内容は攻撃者が決められる
- 攻撃がブラウザで実行
CORFによる攻撃手法について理解したので、次は防御策について見ていきましょう。
CSRF対策手法
CSRFの防御策をいくつか紹介していきます。
トークンを利用する
CSRFトークンを利用することが一般的です。以下は、CSRFトークンを使った防御手法の例です。
<form action="<https://example.com/cart/add>" method="POST">
<input type="hidden" name="item_id" value="1234">
<input type="hidden" name="quantity" value="1">
<input type="hidden" name="csrf_token" value="abcdefghijklmno">
<button type="submit">Add to Cart</button>
<
Webアプリケーションがユーザーに対して発行するCSRFトークンを、フォームに含めることで、攻撃者はトークンを知らない限り、意図しない操作を行わせることができなくなります。
どういうことかというと、トークンを用いたCSRF対策ではセッション毎に異なる値のトークンを発行します。そしてサーバは受け取ったリクエストと、セッションにあるトークンの値が合致しているかで攻撃者によるリクエストではないかを確認することができるということです。
実際のところ多くのフレームワークやライブラリは自動でセッション毎にトークンの発行を行なっているので開発者は特段対応するケースは少ないかもしれません。(対応しているかの確認は必須)
SameSite Cookieを利用する
SameSite Cookieとは、Cookieの属性の一つで、Cookieが送信元サイト以外のサイトに送信されないようにする機能です。
CSRFはログイン後の重要な処理に対して攻撃をかける手法なので、ログイン済みのアカウントのセッション情報を格納するCookieを制御することで、攻撃されない状態をつくることができます。
プリペンティブな手法ですね。
SameSite属性には、以下の値があります。
- Strict
- Cookieが送信元サイト以外に送信されないようになります。
- Lax
- URLが変わるような画面遷移&GETメソッドリクエストであればCookieを送信し、そのほかのクロスサイトであれば送信しない
- None
- 外部サイトにもCookieが送信されます。
以下は、PHPでSameSite属性を指定する例です。
setcookie('cookie_name', 'cookie_value', [
'expires' => time() + 3600,
'path' => '/',
'domain' => 'example.com',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
上記の例では、Secure属性とHttpOnly属性も指定しています。Secure属性は、HTTPSプロトコルでの通信時にのみCookieを送信するように指定するもので、HttpOnly属性は、JavaScriptからCookieにアクセスできないようにするものです。
Samesite属性の値にStrictを指定することで、CookieはSameSite属性を持つリクエストのみに送信されます。
CORSを利用する
CORSを利用したCSRFの対策方法について、以下に具体的なコードを用いて解説します。
まず、リクエストにOriginヘッダーを含めるために、以下のようにXMLHttpRequestオブジェクトを生成します。
var xhr = new XMLHttpRequest();
xhr.open('POST', '<https://example.com/api>');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.withCredentials = true;
xhr.setRequestHeader('Origin', '<https://example.com>');
xhr.send(JSON.stringify({
name: 'example'
}));
このコードでは、xhrオブジェクトを生成し、リクエストヘッダーにOriginを含めるためにsetRequestHeaderメソッドを使用しています。また、withCredentialsプロパティをtrueに設定することで、Cookieを送信することができます。
次に、サーバー側でレスポンスヘッダーにAccess-Control-Allow-Originを含めるために、以下のように設定します。
const express = require('express');
const app = express();
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '<https://example.com>');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
res.header('Access-Control-Allow-Credentials', 'true');
next();
});
app.post('/api', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Example app listening on port 3000!');
});
このコードでは、Access-Control-Allow-Originヘッダーを含めるように設定しています。
また、Access-Control-Allow-HeadersとAccess-Control-Allow-Credentialsも設定しています。
リクエストにOriginヘッダーを含め、サーバー側でAccess-Control-Allow-Originヘッダーを含めることで、攻撃者が異なるオリジンからのリクエストを送信することを防止し、Webアプリケーションの脆弱性を低減することができます。
Discussion