こんにちは。小國です。
symfony/security-csrfを触ってレガシーアプリにCSRF対策をしようと思ったのですが、使い方がググってもあまり出てこなかったので、まとめておこうと思います。
CSRFとは?
ウェブサイトの中には、サービスの提供に際しログイン機能を設けているものがあります。ここで、ログインした利用者からのリクエストについて、その利用者が意図したリクエストであるかどうかを識別する仕組みを持たないウェブサイトは、外部サイトを経由した悪意のあるリクエストを受け入れてしまう場合があります。このようなウェブサイトにログインした利用者は、悪意のある人が用意した罠により、利用者が予期しない処理を実行させられてしまう可能性があります。このような問題を「CSRF(Cross-Site Request Forgeries/クロスサイト・リクエスト・フォージェリ)の脆弱性」と呼び、これを悪用した攻撃を、「CSRF 攻撃」と呼びます。
「安全なウェブサイトの作り方」より
CSRF対策はいくつも方法があるのですが、
Laravelの@csrfのようにトークンを利用して、アプリケーション側で正規のアクセスかどうかを検証するのが効果的な対策の1つかと思います。
その他にも、サーバー側でOriginヘッダーをチェックするなどの対策方法もあるので、併せてご検討ください。
なお、CSRF対策は以下の資料がとてもわかり易かったので参考にしてください(資料のことは同僚に教えてもらいましたw)。
https://fortee.jp/phperkaigi-2024/proposal/0d0f8507-0a53-46f6-bca6-23386d78f17f使い方
それではsymfony/security-csrfを使って、CSRF対策の実装をしていきます。
symfony/security-csrfのインストール
1composer symfony/security-csrf
CSRFトークンマネージャーの作成
1use Symfony\Component\Security\Csrf\CsrfTokenManager;
2use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator;
3use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage;
4
5// CSRFトークンマネージャーの作成
6$tokenGenerator = new UriSafeTokenGenerator();
7$tokenStorage = new NativeSessionTokenStorage();
8$csrfTokenManager = new CsrfTokenManager($tokenGenerator, $tokenStorage);
トークンの生成とフォームの組み込み
1// トークンを生成
2$csrfToken = $csrfTokenManager->getToken('login')->getValue();
3
4// トークンをフォームに組み込む
5echo '<input type="hidden" name="csrf_token" value="' . htmlspecialchars($csrfToken) . '">';
トークンの検証
1$token = $_POST['csrf_token'];
2if (!$csrfTokenManager->isTokenValid(new CsrfToken('login', $token))) {
3 exit('Invalid CSRF token.');
4}
実装は以上になります!
このようなフォームのトークンを利用してのCSRF対策を行う際には、フォームがHTTPSで送信されていることが重要です。これにより、トークンが盗まれるリスクを低減できます。
また、symfony/security-csrfはトークンの有効期限や使用回数の設定オプションは提供されていないようです。アプリケーションのニーズに応じてCsrfTokenManagerInterfaceを実装してトークンの管理方法をカスタマイズすることになりそうです。
1use Symfony\Component\Security\Csrf\CsrfToken;
2use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
3
4class CustomCsrfTokenManager implements CsrfTokenManagerInterface
5{
6 public function getToken($tokenId): CsrfToken
7 {
8 }
9
10 public function refreshToken($tokenId): CsrfToken
11 {
12 }
13
14 public function removeToken($tokenId): ?string
15 {
16 }
17
18 public function isTokenValid(CsrfToken $token): bool
19 {
20 }
21}
それでは!