🔥
CSRFについて
CSRFとは
クロスサイト・リクエストフォージェリを略してCSRFです
- クロスサイト(Cross-Site):
- 「サイトを跨ぐ」という意味
- 攻撃が異なるウェブサイト間で行われることを示す
- リクエスト(Request):
- ウェブブラウザがウェブサーバーに送信する要求のこと
- フォージェリ(Forgery):
- 「偽造」や「詐称」を意味する
- 正規のユーザーになりすまして不正なリクエストを作成することを指す
CSRF攻撃
ユーザーが意図しないリクエストをユーザーの認証情報を使って送信させる攻撃です。この攻撃により、攻撃者はユーザーが持つ特権(例: ログイン済みのセッションやクッキー)を利用して、不正な操作を実行させること。
脆弱性が生まれる原因
- form要素のaction属性にはどのドメインのURLでも指定できる
- クロスドメインアクセス(異なるオリジンへアクセスすること)はブラウザに同一オリジンポリシーがあるためできなくなっている。個人情報やセッションデータが不正に他のドメインに漏れないようにするため。これを意図的に許可するのがCORS(クロスオリジンリソースシェアリング)
- CORSを設定しなくても、クロスドメインアクセスが許可されているものがいくつかある
- その中に**「formのaction属性」**がある。クロスドメインアクセスができることでの脆弱性を利用して、CSRF攻撃ができる
- CORSを設定しなくても、クロスドメインアクセスが許可されているものがいくつかある
- クロスドメインアクセス(異なるオリジンへアクセスすること)はブラウザに同一オリジンポリシーがあるためできなくなっている。個人情報やセッションデータが不正に他のドメインに漏れないようにするため。これを意図的に許可するのがCORS(クロスオリジンリソースシェアリング)
- クッキーに保管されたセッションIDは、対象サイトに自動的に送信される
- 罠サイト経由のリクエストでもセッションIDのクッキー値が送信されるので、認証された状態で攻撃リクエストが送信される
パスワードが変更させられる例
- ⓪利用者がexmple.jpにログインしている
- ①攻撃者が罠を用意する
- ②被害者が罠を閲覧する
- ③罠のJavaScriptにより、被害者のブラウザ上で攻撃対象サイトに対し、新しいパスワードがPOSTメソッドにより送信される ※クッキーとして、攻撃対象サイトのセッションIDが不要されている
- ④パスワードが変更される
対策
- CSRF対策の必要なページを区別する
- 正規利用者の意図したリクエストを確認できるように実装する
方法
- 秘密情報(トークン)の埋め込み
- パスワード再入力
- 以下の要件を満たしているならパスワード再入力でCSRF対策は可能
- 重要な処理に先立って、正規利用者であることを再確認する
- 共有PCで別人が操作している状況などがなく、本当に正規の利用者であることを確認する
- ただし、ログアウトなどの前に毎回パスワードを再入力することになるなどUXが悪くなる懸念もある
- 以下の要件を満たしているならパスワード再入力でCSRF対策は可能
- Refererのチェック
- Refererと言うのはHTTPリクエストのリンク元のURLを示す情報
- CSRF攻撃時のHTTPリクエストにおいてReferer以外は同じ情報を送信している状態になる
- しかしRefererの値を通常のWebアプリケーションではチェックしないため、チェックするようにすることで対策をする手法
- ただしこれをしてしまうと利用者がRefererが送信されないように設定している場合にそのページの実行ができなくなる懸念がある
- しかしRefererの値を通常のWebアプリケーションではチェックしないため、チェックするようにすることで対策をする手法
- CSRF攻撃時のHTTPリクエストにおいてReferer以外は同じ情報を送信している状態になる
- Refererと言うのはHTTPリクエストのリンク元のURLを示す情報
方法の比較
トークンの埋め込み | パスワード再入力 | Referer確認 | |
---|---|---|---|
開発工数 | 中 | 中 | 小 |
利用者への影響 | なし | パスワード入力の手間 | Refererをオフにしている利用者が使えない |
推奨する利用シーン | 一般的な対策方法であり、あらゆる場面で利用推奨 | 成りすまし対策や、確認を強く求めるような要件がある画面 | 利用者の環境を限定でkりう既存アプリケーションのCSRF対策 |
CSRFライブラリの使い方
こちらのライブラリを使用して実装します
前述した方法の「秘密情報(トークン)の埋め込み」の手法になります
手順
インストール(TypeScriptを使用するため型定義もインストール)
npm install csrf @types/csrf
サーバー側でcsrf
インスタンスを作成し、ミドルウェアとして設定する
app.ts
import Tokens from "csrf";
// csrfに関する箇所以外のコードは省略
// csrfインスタンスを作成
const tokens = new Tokens();
// CSRFミドルウェアを定義
app.use((req, res, next) => {
const secret = tokens.secretSync();
const token = tokens.create(secret);
res.locals.csrfToken = token;
res.cookie("csrf-secret", secret, { httpOnly: true });
res.setHeader("xsrf-token", token);
next();
});
- secretとtokenの違い
- secret(シークレット):
- これは、サーバーサイドで生成される秘密の文字列
- トークンを生成し、後で検証するために使用される
- 外部に露出させてはいけない機密情報
- token(トークン):
- secretを使って生成される一意の文字列
- クライアントに送信され、リクエスト時に送り返されるもの
- 実際のリクエストに含まれる、検証可能な値
- secret(シークレット):
- secretとtokenの生成プロセス
- サーバーは各セッションごとにユニークなsecretを生成
- このsecretを使用して、tokenを生成
- secretはcookieに保存され、tokenはレスポンスヘッダーまたはレスポンスボディ(例:隠しフォームフィールド)で送信
- リクエスト時の流れ
- クライアントがリクエストを送信する際、cookieに保存されたsecretは自動的にサーバーに送信
- 同時に、クライアントはtokenをリクエストヘッダーまたはリクエストボディに含めて送信
- クライアントがリクエストを送信する際、cookieに保存されたsecretは自動的にサーバーに送信
- サーバーでの検証:
- サーバーはリクエストからsecret(cookie)とtoken(ヘッダーまたはボディ)を取得
- サーバーは受け取ったsecretを使用して、受け取ったtokenが有効かどうかを検証
- シークレットはトークンを生成するための「種」として使用される
- 同じシークレットから生成されたトークンのみが有効となる
- ポイント
- secretはtokenを生成・検証するための「鍵」のような役割を果たす
app.ts
app.post("/submit", (req, res) => {
const secret = req.cookies["csrf-secret"];
if (tokens.verify(secret, req.body._csrf)) {
res.send("Success! Tokenが一致しました");
} else {
res.status(403).send("Errorになったよ! Tokenが一致しません");
}
});
-
const secret = req.cookies["csrf-secret"];
- リクエストのクッキーから 'csrf-secret' の値を取得している
- この 'secret' は、前のレスポンスでサーバーがクライアントに送信したもの
- httpOnly フラグ付きで保存されているため、JavaScriptからはアクセスできない
-
tokens.verify(secret, req.body._csrf)
-
tokens.verify()
メソッドを使用して、CSRFトークンの検証を行っている - 第一引数
secret
は、クッキーから取得したシークレット値 - 第二引数
req.body._csrf
は、フォームのhiddenフィールドから送信されたCSRFトークン - この関数は、シークレットを使用してトークンを検証し、有効な場合は
true
を返す
-
- 条件分岐:
- トークンが有効な場合(
verify()
がtrue
を返す)- "Success! Tokenが一致しました" というメッセージを送信する
- トークンが無効な場合
- HTTP状態コード 403 (Forbidden) と共に、"Errorになったよ! Tokenが一致しません" というメッセージを送信する
- トークンが有効な場合(
index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSRF Protection Demo</title>
</head>
<body>
<h1>CSRF Protection Demo</h1>
<form action="/submit" method="POST">
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<input type="text" name="data" placeholder="Enter some data">
<button type="submit">Submit</button>
</form>
</body>
</html>
- hiddenフィールドにcsfrTokenを仕込む
- cookieにsecretがあり、ブラウザのhiddenフィールドにはtokenを仕込んである状態になる
- この2つが送信されて、サーバー側でtokenとsecretの整合性を検証することで、csrf対策になる
- つまり罠サイト(secretから生成されたtokenを持たない)画面からのリクエストを弾くことができる
- この2つが送信されて、サーバー側でtokenとsecretの整合性を検証することで、csrf対策になる
実装してみた画面
- hiddenフィールドのinputにtokenが埋め込まれており、こちらの内容を削除したり編集するとsubmitできなくなる
成功パターン
失敗パターン(tokenを編集)
最後に
今、第2版 体系的に学ぶ 安全なWebアプリケーションの作り方 の書籍を読んでいて、今回csrfについてアウトプットしました。
最後まで読んでくださりありがとうございました!
Discussion