初心者も押さえておくべきWebセキュリティ vol.2 『クロスサイトリクエストフォージェリ(CSRF)』からの保護対策

JavaScript

クロスサイトリクエストフォージェリ(以下、CSRF)は、Webセキュリティの重要な脅威の一つです。攻撃者は、被害者のアカウントを悪用し、不正なリクエストを送信することで、ユーザーの意図しないアクションを実行させることができます。この記事では、CSRF攻撃からの保護方法を解説します。セキュリティの初心者でも理解しやすいように、基本的な対策方法やベストプラクティスを紹介します。正しい対策を講じることで、ウェブアプリケーションのセキュリティを向上させ、ユーザーの安全を確保しましょう。

クロスサイトスクリプティング(XSS)については『初心者も押さえておくべきWebセキュリティ vol.1 JavaScript『クロスサイトスクリプティング(XSS)』 効果的な対策方法と最適な方法』を参照してください。

リクエストの検証とCSRFトークンの使用

CSRF攻撃を防ぐために、リクエストの送信元を検証する手法です。セッションに関連する情報(例:CSRFトークン)をリクエストパラメータやヘッダに含め、サーバーサイドで検証します。攻撃者は他のドメインからのリクエストを送信する際に、この検証を回避できません。

具体的にはサーバーサイドでフォームを生成することを前提として、<form>タグの下層に<input>に一意の文字列を設置することで、セッションと同一かどうかを判別します。セッションと<input>の文字列が一致した場合は、処理を続行させ、一致しない場合は処理を打ち切ります。

以下はPHPで基本的なリクエストの検証を行うソースコードです。(注:フォームの表示と送信ページが同一の場合の例です。)

<?php
// CSRFトークンの生成とセットアップ
session_start();
if (!isset($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); // (1)
}

// リクエストの検証と処理
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $submittedToken = $_POST['_csrf'];

    // CSRFトークンの検証
    if (!hash_equals($_SESSION['csrf_token'], $submittedToken)) {
        http_response_code(403);
        die('Invalid CSRF token');
    }

    // リクエストの処理
    $message = $_POST['message'];
    // ...
    echo 'Message received';
}
?>

<!-- フォームの表示 -->
<form action="" method="POST">
    <input type="hidden" name="_csrf" value="<?php echo $_SESSION['csrf_token']; ?>"> <!-- (2) -->
    <input type="text" name="message" placeholder="Enter your message">
    <button type="submit">Submit</button>
</form>

(1)bin2hex(random_bytes(32))

csrf_tokenというセッション(※)にbin2hex(random_bytes(32))という値をセットしています。このコードはrandom_bytes(32)で32バイトのランダムなバイナリデータを生成したあと、bin2hex()メソッドで16進数の文字列に変換しています。ランダム生成なのでこのセッション以外で同一の文字列が取得できる可能性は限りなく低く、一致不一致を判定する際に非常に精度が高くなります。

この生成文字列の例としては、e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4のような値がセッションに格納されます。

(※)リクエストの検証用の「セッション名」「inputタグのname」にはCSRFが推測されにくい名前を付けることをおすすめします

(2)inputタグ(hidden)の値に(1)の生成値を設定

<input type="hidden" name="_csrf" value="<?php echo $_SESSION['csrf_token']; ?>">

PHPでHTMLのフォームを生成する際にtype="hidden"<input>に(1)で生成したランダムな文字列が値になるようにします。先ほどのサンプル文字列を使うとHTML上では

<input type="hidden" name="_csrf" value="e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4">

となります。

リクエストの検証のロジックとしては、送信時に(2)の値と(1)のセッション値が同一かどうかを判別するというものです。この手法を用いることで同一ブラウザからのフォームの送信かどうかをジャッジすることが可能です。もし、同一ではない場合は不審なアクセスとして、サーバー側の受信処理をシャットダウンしてしまうということですね。

ちなみにですが、このリクエストの検証を行う際に、送信前にJavaScriptで検証して、エラーであれば送信させないということも可能です。

    // フォーム送信時のイベントハンドラ
    document.getElementById('myForm').addEventListener('submit', function(event) {
        // CSRFトークンの取得
        var csrfToken = document.querySelector('input[name="_csrf"]').value;

        // CSRFトークンの検証
        if (csrfToken !== '') {
            event.preventDefault(); // フォームの送信をキャンセル
            // サーバーサイドにリクエストを送信するか、エラーメッセージを表示するなどの処理を行う
            console.log('Invalid CSRF token');
        }
    });

CSRF対策用の<input>が無い場合は送信をキャンセルしているだけです。不要なアクセスをシャットダウンしサーバーの負荷を軽減することが出来ます。

SameSite属性の使用

SameSite属性は、クッキーを使用する際にセキュリティを向上させるための属性です。SameSite属性を指定することで、クッキーの送信方法を制御し、クロスサイトリクエストを防ぐことができます。SameSite属性には以下の3つの値を指定が可能です。

  1. SameSite=None:SameSite属性を指定せず、クロスサイトリクエストに対してもクッキーが送信されるようにします。ただし、この値を使用する場合はSecure属性(HTTPS)も必要です。
  2. SameSite=Strict同一サイト内からのリクエストのみに対してクッキーが送信されるようにします。クロスサイトリクエストではクッキーが送信されません
  3. SameSite=Lax:Strictと同様に同一サイト内からのリクエストのみに対してクッキーが送信されるようにします。ただし、一部のクロスサイトリクエスト(外部リンクによるGETリクエストなど)ではクッキーが送信される場合があります。

SameSite属性の使用により、CSRF攻撃やクロスサイトスクリプティング(XSS)攻撃からの保護が向上します。特にStrictモードでは、クッキーの送信が厳密に制限されるため、セキュリティ上のリスクを低減することができます。

// クッキーの設定
setcookie('my_cookie', 'cookie_value', [
    'expires' => time() + 3600, // 有効期限(1時間)
    'path' => '/', // クッキーが有効なパス
    'domain' => 'example.com', // クッキーが有効なドメイン
    'secure' => true, // HTTPSでのみクッキーの送信
    'httponly' => true, // JavaScriptからのアクセスを制限
    'samesite' => 'Strict', // SameSite属性の設定
]);

この例ではsetcookie() 関数を使用してクッキーを設定しています。samesite オプションに 'Strict' を指定することで、SameSite属性をStrictモードに設定しています。

リファラーチェック

リファラーチェックは、Webサーバーが受け取ったリクエストのリファラーヘッダーを検証することで、CSRF攻撃を防ぐためのセキュリティ対策です。

リファラーヘッダーにはリクエスト元のURLが含まれており、通常はウェブサーバーに対してどのページからのリクエストかを示すために使用されます。受け取ったリクエストのリファラーヘッダーを検証し、許可されたドメインからのリクエストであることを確認します。

Webサーバーの設定ファイル(Apache)

Apacheの場合、.htaccess ファイルやサーバーの設定ファイル(httpd.conf)でリファラーチェックを有効化できます。以下は .htaccess ファイルの例です。

# リファラーチェックを有効化する設定
RewriteEngine On
RewriteCond %{REQUEST_METHOD} POST
RewriteCond %{HTTP_REFERER} !^https://(www\.)?example.com/ [NC]
RewriteRule ^ - [F]

この設定では、POSTリクエストの場合にリファラーチェックを行い、許可されたドメイン(example.com)からのリクエストでなければ403 Forbiddenの応答を返します。

サーバーサイドプログラムでの実装

独自のコードでリファラーチェックを実装することもできます。受け取ったリクエストのリファラーヘッダーを取得し、許可されたドメインと比較してチェックを行います。以下はPHPの例です。

$referer = $_SERVER['HTTP_REFERER'];
$allowedDomain = 'https://www.example.com';

if ($referer !== $allowedDomain) {
    // リファラーチェックに失敗した場合の処理
    die("Invalid referer");
}

※一部のブラウザではリファラーヘッダーが送信されない設定になっている場合や、簡単に偽装可能な場合があるため、リファラーチェックと組み合わせてトークンベースのCSRF対策も併用することが推奨されています。

タイトルとURLをコピーしました