PHPを使用してクロスサイトリクエストフォージェリ(CSRF)トークンを適切に追加する方法


96

私は私のウェブサイトのフォームにいくつかのセキュリティを追加しようとしています。フォームの1つはAJAXを使用しており、もう1つは単純な「お問い合わせ」フォームです。CSRFトークンを追加しようとしています。私が抱えている問題は、トークンが時々HTMLの「値」にしか表示されないことです。残りの時間、値は空です。AJAXフォームで使用しているコードは次のとおりです。

PHP:

if (!isset($_SESSION)) {
    session_start();
$_SESSION['formStarted'] = true;
}
if (!isset($_SESSION['token']))
{$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;

}

HTML

 <form>
//...
<input type="hidden" name="token" value="<?php echo $token; ?>" />
//...
</form>

助言がありますか?


好奇心旺盛、何token_timeに使うの?
zerkms

@zerkms現在使用していませんtoken_time。トークンが有効な時間を制限しようとしていましたが、まだコードを完全に実装していません。明確にするために、上記の質問からは削除しました。
Ken

1
@ケン:ユーザーがフォームを開いたときにケースを取得して投稿し、無効なトークンを取得できるようにするには?(無効化されているため)
zerkms

@zerkms:ありがとう、でも少し混乱しています。例を教えていただけませんか?
Ken

2
@ケン:確かに。トークンが午前10時に期限切れになるとします。現在は午前9時59分です。ユーザーがフォームを開き、トークンを取得します(まだ有効です)。次に、ユーザーは2分間フォームに入力して送信します。現在午前10時1分である限り-トークンは無効として扱われているため、ユーザーはフォームエラーを受け取ります。
zerkms

回答:


286

セキュリティコードについては、この方法でトークンを生成しないでください。 $token = md5(uniqid(rand(), TRUE));

これを試してください:

CSRFトークンの生成

PHP 7

session_start();
if (empty($_SESSION['token'])) {
    $_SESSION['token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['token'];

追記:一、私の雇用者のオープンソースプロジェクトは、バックポートへの取り組みですrandom_bytes()し、random_int()PHP 5プロジェクトに。これはMITライセンスで、GithubおよびComposerでparagonie / random_compatとして利用できます。

PHP 5.3以降(またはext-mcryptを使用)

session_start();
if (empty($_SESSION['token'])) {
    if (function_exists('mcrypt_create_iv')) {
        $_SESSION['token'] = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM));
    } else {
        $_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(32));
    }
}
$token = $_SESSION['token'];

CSRFトークンの確認

を使用し==たり===、使用したりしないでくださいhash_equals()(PHP 5.6+のみですが、以前のバージョンのhash-compatライブラリで使用できます)。

if (!empty($_POST['token'])) {
    if (hash_equals($_SESSION['token'], $_POST['token'])) {
         // Proceed to process the form data
    } else {
         // Log this as a warning and keep an eye on these attempts
    }
}

フォームごとのトークンをさらに進める

さらに、を使用して、特定のフォームでのみトークンを使用できるように制限できますhash_hmac()。HMACは特定のキー付きハッシュ関数であり、弱いハッシュ関数(MD5など)を使用しても安全です。ただし、代わりにハッシュ関数のSHA-2ファミリーを使用することをお勧めします。

まず、HMACキーとして使用する2番目のトークンを生成し、次のようなロジックを使用してレンダリングします。

<input type="hidden" name="token" value="<?php
    echo hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
?>" />

そして、トークンを検証するときに合同操作を使用します:

$calc = hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
if (hash_equals($calc, $_POST['token'])) {
    // Continue...
}

あるフォームに対して生成されたトークンは、知らないうちに別のコンテキストで再利用することはできません$_SESSION['second_token']ページにドロップしたトークンとは別のトークンをHMACキーとして使用することが重要です。

ボーナス:ハイブリッドアプローチ+ Twig統合

Twigテンプレートエンジンを使用する人は誰でも、このフィルターをTwig環境に追加することにより、簡素化されたデュアルストラテジーの恩恵を受けることができます。

$twigEnv->addFunction(
    new \Twig_SimpleFunction(
        'form_token',
        function($lock_to = null) {
            if (empty($_SESSION['token'])) {
                $_SESSION['token'] = bin2hex(random_bytes(32));
            }
            if (empty($_SESSION['token2'])) {
                $_SESSION['token2'] = random_bytes(32);
            }
            if (empty($lock_to)) {
                return $_SESSION['token'];
            }
            return hash_hmac('sha256', $lock_to, $_SESSION['token2']);
        }
    )
);

このTwig関数を使用すると、次のように両方の汎用トークンを使用できます。

<input type="hidden" name="token" value="{{ form_token() }}" />

またはロックダウンされたバリアント:

<input type="hidden" name="token" value="{{ form_token('/my_form.php') }}" />

Twigはテンプレートのレンダリングのみに関係しています。それでも、トークンを適切に検証する必要があります。私の意見では、Twig戦略は、最大限のセキュリティの可能性を維持しながら、柔軟性とシンプルさを向上させます。


使い捨てCSRFトークン

各CSRFトークンを1回だけ使用できるというセキュリティ要件がある場合は、検証が成功するたびに、最も簡単な戦略でトークンを再生成します。ただし、これを行うと以前のトークンがすべて無効になるため、一度に複数のタブを閲覧するユーザーとうまく混合できません。

Paragon Initiative Enterprisesは、これらのコーナーケース用のAnti-CSRFライブラリを維持しています。1回限りのフォームごとのトークンでのみ機能します。十分なトークンがセッションデータに格納されている場合(デフォルトの構成:65535)、最初に未使用の最も古いトークンが循環します。


いいですが、ユーザーがフォームを送信した後に$ tokenを変更するにはどうすればよいですか?あなたのケースでは、ユーザーセッションに使用される1つのトークン。
Akam 2016年

1
github.com/paragonie/anti-csrfの実装方法をよく見てください。トークンは使い捨てですが、複数を格納します。
Scott Arciszewski、2016年

@ScottArciszewskiセッションIDからシークレットを使用してメッセージダイジェストを生成し、受信したCSRFトークンダイジェストを以前のシークレットを使用してセッションIDを再度ハッシュして比較するとしたら、どう思いますか?どういう意味かご理解いただければ幸いです。
MNR

1
CSRFトークンの確認について質問があります。$ _POST ['token']が空の場合、この投稿リクエストはトークンなしで送信されたので、続行しないでください。
ヒロキ

1
これは、HTMLフォームにエコーされ、攻撃者が偽造できないように予測不能にしたいからです。攻撃者が偽装できるため、単に「このフォームが合法である」というだけでなく、ここでチャレンジ/レスポンス認証を実際に実装しています。
Scott Arciszewski、2017年

24

セキュリティ警告md5(uniqid(rand(), TRUE))乱数を生成する安全な方法ではありません。暗号化された安全な乱数ジェネレータを利用する詳細とソリューションについては、この回答を参照してください。

ifでelseが必要なようです。

if (!isset($_SESSION['token'])) {
    $token = md5(uniqid(rand(), TRUE));
    $_SESSION['token'] = $token;
    $_SESSION['token_time'] = time();
}
else
{
    $token = $_SESSION['token'];
}

11
注:私はmd5(uniqid(rand(), TRUE));セキュリティコンテキストを信頼しません。
Scott Arciszewski、2015年

2

変数$tokenはそこにあるときにセッションから取得されていません

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.