最近セキュリティについて学習しているのですが、クロスサイトリクエストフォージェリがよくわかりません。
確かにわかりにくいですよね。クロスサイトリクエストフォージェリとは何か、Laravelはどんな対策をしているのか一緒に見ていきましょう!
この記事がおすすめの方
- セキュリティを学び始めた方
- 具体的にアプリケーションがどのような対策をとっているのか知りたい方
- bladeファイルでなぜ@csrfの記述が必要かわからない方
まずは結論から!
クロスサイトリクエストフォージェリとは「悪意のあるサイトに誘導し、ユーザーが意図しないリクエストをWebサーバーに送信させて、データの漏洩や不正なアプリケーションの実行を引き起こすもの」です。
Laravelは「CSRFトークンを発行して、ユーザーが送信したリクエストか確認」して対処しています。
これだけだと、非常にわかりにくいので、詳しく見ていきましょう!
クロスサイトリクエストフォージェリとは
クロスサイトリクエストフォージェリとは「悪意のあるサイトに誘導し、ユーザーが意図しないリクエストをWebサーバーに送信させて、データの漏洩や不正なアプリケーションの実行を引き起こすもの」です。
Cross-Site Request Forgeriesの頭文字をとって「CSRF(シーサーフ)」と呼ばれます。Request Forgeriesを直訳するとリクエストの偽造。その名の通り、リクエストを偽造して送信することで、なんらかの悪さをします。
リクエストがよくわからない方はこちらから
ユーザー「はむ」と悪意のある「タック」がいる場合で、具体的に考えてみます。
田中さんに1万円を送金しないと!
hamuが銀行サイトにログインして田中さんに1万円送金しようとしています。その時のリクエストが「http://bank.com/transfer.do?acct=TANAKA&amount=10,000」だとします。
その時、一通のメールが届きました。
このリンクをクリックしてくれれば、僕に100万円送金される。しめしめ
焦ったはむはこのリンクをクリックしてしまいます。
すると、はむのブラウザから「http://bank.com/transfer.do?acct=TACK&amount=1,000,000」というリクエストが銀行サイトに送られてしまいます。
はむが銀行サイトにログインしていると、銀行サイトは、はむからの正常なリクエストだと勘違いしてタックの口座に送金してしまいます。
まとめます。
- SNS、掲示板やメールなどに悪意のあるリンクをクリックすると、不正なリクエストが送られる
- ログインしている場合、サービス側が本人のリクエストだと勘違いして、送金や書き込みなどの動作を実行してしまう
ユーザー側の対策は次の2点が考えられます。
- 不審なWebサイトにはアクセスしない
- サービス利用後、ログアウトしておく
さて、ここからが本題。Laravelがどんな対策を講じているのか見ていきます。
Laravelの対策
Laravelは「CSRFトークンを発行して、ユーザーが送信したリクエストかを確認」しています。
そもそもの問題点は「不正なリクエストと本人のリクエストをアプリ側で識別できていないこと」でした。
このリクエストを識別する仕組みが「CSRFトークンの発行とミドルウェアでの検証」です。
CSRFトークンの自動発行
トークンは「合言葉」だと考えるとわかりやすいです。合言葉をつくっておいて、後で照合することで本人であることを確かめます。
実際にCSRFトークンを見てみると、暗号化された文字列であることがわかります。
//以下のどちらかで取得 dd(csrf_token()); dd(session()->token()); //"Uq5J3YBVPA4kF9um3u66apGxITUT3sApO4dS3CZC"
このように、LaravelはユーザーのセッションごとにCSRFトークンを自動発行しています。
フォームにCSRFトークンを含ませる
リクエストを送る際にCSRFトークンを一緒に送信して、セッションに保存したCSRFトークンの値と照合することが目的です。
bladeファイルのformタグ内に@csrfを記載することで、非表示でCSRFトークンを送信することができます。
<form method="POST" action="/profile">
@csrf
</form>
つまり、以下の記述と同じことが@csrfで実現できます。
<form method="POST" action="/profile">
<input type="hidden" name="_token" value="{{ csrf_token() }}" />
</form>
値を確認すると、_tokenというキーに対して、CSRFトークンが送信されていることがわかります。
logger($request->input()); //array ( // '_token' => 'Uq5J3YBVPA4kF9um3u66apGxITUT3sApO4dS3CZC',
ミドルウェアで検証
セッション内で保存してあるCSRFトークンと送信されたCSRFトークンの2つがそろいました。
この2つをミドルウェア内で比較検証しています。
ミドルウェアはざっくりいうと「コントローラー内の本処理が始まる前にしておきたい処理を行う機能」です。
具体的には、次のファイルで確認しています。
src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php
public function handle($request, Closure $next) { if ( $this->isReading($request) || $this->runningUnitTests() || $this->inExceptArray($request) || $this->tokensMatch($request) ) { return tap($next($request), function ($response) use ($request) { if ($this->shouldAddXsrfTokenCookie()) { $this->addCookieToResponse($request, $response); } }); } throw new TokenMismatchException('CSRF token mismatch.'); }
この中のtokensMatchメソッドでリクエストのCSRFトークンとセッションに保存されているCSRFトークンが一致しているか確認しています。
protected function tokensMatch($request) { $token = $this->getTokenFromRequest($request); //リクエストからCSRFトークン取得 return is_string($request->session()->token()) && //セッションに保存されているCSRFトークンが文字列であることを確認 is_string($token) && //リクエストからのCSRFトークンが文字列であることを確認 hash_equals($request->session()->token(), $token); //一致を確認 }
まとめ
最後にまとめていきましょう。
クロスサイトリクエストフォージェリ(CSRF)
Laravelの対策
- CSRFトークンを発行
- リクエスト内にCSRFトークンを含めて送信させる
- ミドルウェアで2つを照合する
フレームワークが裏側で行っている処理のおかげで安心してアプリケーションが作成できることがよくわかります。
コメント