![見出し画像](https://assets.st-note.com/production/uploads/images/82784880/rectangle_large_type_2_bfd6aa07f0313d734b665572f3efd4ba.jpeg?width=1200)
【 PHP学習 #18 】 セキュリティ 「CSRF」の対策 FIREへの旅路 ♯468
セキュリティ対策を学びます。
今回は、CSRF という攻撃です。
【 CSRF 】 Cross-Site Request Forgeries
クロスサイトリクエストフォージェリ は、Webアプリケーションの脆弱性の一つもしくはそれを利用した攻撃。略称はCSRF、またはXSRF。リクエスト強要、セッションライディング とも呼ばれる。1990年代はイメタグ攻撃とも呼ばれていた
CERFはシーサーフと呼びます。
Forgeriesは偽装という意味です。
▶︎ 仕組み
攻撃者は罠となるサイトを作り、そこにユーザーが訪問するように誘導します。
この罠サイトにアクセスしたときに、ユーザーが自身のSNSのアカウントにログイン中だったとします。
ログイン状態で罠サイトにアクセスすると、罠サイトから、SNSに偽のリクエストが送信され、実行されてしまいます。
▶︎ 事例
具体的に何が起こるかというと、ユーザーのアカウントで勝手に記事が投稿されたり、アカウントの設定が変更されたりといった被害が出ます。
いわゆるアカウントを乗っ取られた状態です。
また、ログインフォームなどパスワードを入力した際に、その情報を抜かれることもあります。
▶︎ 対策
CSRFは、偽のinputからのリクエストがサーバーに送られるので、
偽かどうかを判別することで対策ができます。
その判別には、合言葉となる「トークン」を発行して判別を行います。
【 $_SESSION 】
$_GETや、$_POSTと同様に、スーパーグローバル変数のひとつです。
$_GETや、$_POSTでは、通信を行う際に、サーバーに送ったデータは一回で消えてしまします。
$_SESSIONでは、データがサーバーに残ります。
この特性を使って、合言葉=トークンを発行し照合をします。
▶︎ 全コード
<?php
session_start();
header( 'X-FRAME-OPTIONS:DENY' );
//スーパーグローバル変数
if ( !empty( $_SESSION ) ) {
echo '<pre>';
var_dump( $_SESSION );
var_dump( $_POST );
echo '</pre>';
}
function h( $str ) {
return htmlspecialchars( $str, ENT_QUOTES, 'UTF-8' );
}
//入力画面=0、確認画面=1、完了画面=2
//表示する内容を切り替える条件を設定する
$pageFlag = 0;
if ( !empty( $_POST[ 'btn_confirm' ] ) ) {
$pageFlag = 1;
}
if ( !empty( $_POST[ 'btn_submit' ] ) ) {
$pageFlag = 2;
}
?>
<!DOCTYPE html>
<meta charset="utf-8">
<head>
</head>
<body>
<!--入力画面-->
<?php if($pageFlag === 0) : ?>
<!--csrfトークンの生成-->
<?php
if ( !isset( $_SESSION['csrfToken'] ) ) {
$csrfToken = bin2hex( random_bytes( 32 ) );
$_SESSION['csrfToken'] = $csrfToken;
}
$token = $_SESSION['csrfToken']
?>
<form method="POST" action="input.php">
氏名
<input type="text" name="your_name" value="<?php if(!empty($_POST['your_name'])) {echo h($_POST['your_name']);} ?>">
<br>
メールアドレス
<input type="text" name="email" value="<?php if(!empty($_POST['email'])) {echo h($_POST['email']);} ?>">
<br>
<input type="submit" name="btn_confirm" value="確認する">
<!--csrfトークンを仕込む-->
<input type="hidden" name="csrf" value="<?php echo $token; ?>" >
</form>
<?php endif; ?>
<!--確認画面-->
<?php if($pageFlag === 1) : ?>
<!--POSTのcsrfとSESSIONのcsrfがあっているか判定する-->
<?php if($_POST['csrf'] === $_SESSION['csrfToken']): ?>
<form method="POST" action="input.php">
氏名
<?php echo h($_POST['your_name']);?> <br>
メールアドレス
<?php echo h($_POST['email']);?> <br>
<!--戻るボタン-->
<input type="submit" name="back" value="戻る">
<!--送信ボタン-->
<input type="submit" name="btn_submit" value="送信する">
<input type="hidden" name="your_name" value="<?php echo h($_POST['your_name']);?>" >
<input type="hidden" name="email" value="<?php echo h($_POST['email']);?>" >
<!--$pageFlagが0から1に変わる時にcsrfの値も消えるので、保存しておく-->
<input type="hidden" name="csrf" value="<?php echo h($_POST['csrf']);?>" >
</form>
<?php endif; ?>
<?php endif; ?>
<!--完了画面-->
<?php if($pageFlag === 2) : ?>
<!--POSTのcsrfとSESSIONのcsrfが一致しているか判定する-->
<?php if($_POST['csrf'] === $_SESSION['csrfToken']): ?>
送信が完了しました
<!--トークンを削除する-->
<?php unset($_SESSION['csrfToken']); ?>
<?php endif; ?>
<?php endif; ?>
</body>
</html>
▶︎ 解説 ①
session_start();
ファイルのトップに書きます。
<?php のすぐ下です。
これでまず、$_SESSIONを使える状態になります。
次は、合言葉=トークンを生成します。
▶︎ 解説② csrfトークンの生成
csrfトークンを生成します。
完成したコードが出来上がるまでを順に解説します。
<!--csrfトークンの生成 ・・・・・・・・・解説②-->
<?php
if ( !isset( $_SESSION['csrfToken'] ) ) {
$csrfToken = bin2hex( random_bytes( 32 ) );
$_SESSION['csrfToken'] = $csrfToken;
}
$token = $_SESSION['csrfToken']
?>
■ トークンの生成
まずは、トークンを生成します。random_bytesという関数を使います。
random_bytes — 暗号論的に安全な、疑似ランダムなバイト列を生成する
<!--csrfトークンの生成 ・・・・・・・・・解説②-->
<?php
random_bytes( 32 ) ;
?>
これでトークンが作られますが、このままでは使えません。
この状態でのトークンはバイナリと呼ばれる機械が認識するためのデータになっていて、文字化けしたような表示のものになります。
これを16進数に変換して使用します。
■ トークンを16進数に変換
bin2hex() ー バイナリのデータを16進表現に変換する
bin2hex( random_bytes( 32 ) )
これで数字と、アルファベットに変換されます。
![](https://assets.st-note.com/img/1657241975797-bHuCSJLxWR.png?width=1200)
このトークンを、変数に格納します。
$csrfToken = bin2hex( random_bytes( 32 ) );
■ トークン生成の条件を設定する
<!--csrfトークンの生成 ・・・・・・・・・解説②-->
<?php
$csrfToken = bin2hex( random_bytes( 32 ) );
?>
この状態では、入力画面が開かれるたびに、新たなトークンが生成されるので、「もしトークンがなかったら」という条件を設定します。
▶︎ isset関数
issetは、設定されているかどうかを判定します。
!isset とすることで、「設定されていなかったら」という条件になります。
何が設定されていないか見るのかというと、
$_SESSIONの連想配列の 'csrfToken'というキーのバリューです。
このキーのバリューが生成されたトークンです。
なので、!issetの引数に、$_SESSION['csrfToken'] を入れます。
if(!isset($_SESSION['csrfToken'])){
さらに、
$_SESSION['csrfToken'] = $csrfToken;
$_SESSION['csrfToken'] は、生成されたトークンが入っていますので、
それを、$csrfTokenに代入します。
完成したコードがこちら
<!--csrfトークンの生成 ・・・・・・・・・解説②-->
<?php
if(!isset($_SESSION['csrfToken'])){
$csrfToken = bin2hex( random_bytes( 32 ) );
$_SESSION['csrfToken'] = $csrfToken;
}
?>
上から見ていくと、
もし( if )
$_SESSIONのキー'csrfToken'の値が、
設定されていない( !isset )場合は、
random_bytes関数で、トークンを生成して、$csrfTokenに代入して、
$_SESSIONのキー'csrfToken'の値に、
$csrfToken (生成されたトークン)を代入する。
このような内容になっています。
最後に、
$token = $_SESSION['csrfToken']
$_SESSION['csrfToken'] を $token の格納します。
つまり、$tokenには、生成されたトークンが入っている状態です。
▶︎ 解説③ csrfトークンを仕込む
$tokenに生成されたトークンを格納したので、これを必要な箇所に設置していきます。
入力画面の「確認する」ボタンのinputの下に書きます。
<!--csrfトークンを仕込む・・・・・・解説③-->
<input type="hidden" name="csrf" value="<?php echo $token; ?>" >
このように、value に $tokenをechoすることで、
nemeの、csrfというキーに対して、
生成されたトークンが値(バリュー)としてechoされる状態になります。
▶︎ 解説④ tokenの一致の確認
次は、入力画面で生成されたトークンの値が、
確認画面で受け取ったトークンの値と一致しているかを確認します。
<!--確認画面-->
<?php if($pageFlag === 1) : ?>
<!--POSTのcsrfとSESSIONのcsrfがあっているか判定する・・・・・・・・解説④-->
<?php if($_POST['csrf'] === $_SESSION['csrfToken']): ?>
if文を使って判定します。
$_POST['csrf'] と $_SESSION['csrfToken']
が一致していれば処理を実行するという内容です。
$_POST['csrf']とは、
入力画面で仕込んだ、このコードのname=' csrf ' です。
<input type="hidden" name="csrf" value="<?php echo $token; ?>" >
これが$_POSTの連想配列のキーとなっています。
つまり、入力画面で生成されたトークン=$tokenが、
POST通信で送信されたトークンの値です。
$_SESSION['csrfToken'] とは、
入力画面で生成されたトークン=$token です。
生成されたトークンと、POST通信されたトークンが一致していれば
偽のページからのリクエストではないことが確認できるということです。
▶︎ 解説 ⑤ tokenの一致の最終確認
<!--完了画面-->
<?php if($pageFlag === 2) : ?>
<!--POSTのcsrfとSESSIONのcsrfが一致しているか判定する・・・・・・・解説⑤-->
<?php if($_POST['csrf'] === $_SESSION['csrfToken']): ?>
送信が完了しました
<!--トークンを削除する-->
<?php unset($_SESSION['csrfToken']); ?>
<?php endif; ?>
<?php endif; ?>
完了画面でも、トークンの一致を確認します。
確認画面と同様のコード書きます。
<?php if($_POST['csrf'] === $_SESSION['csrfToken']): ?>
■ トークンの削除
最終的に、生成したトークンを削除します。
<!--トークンを削除する-->
<?php unset($_SESSION['csrfToken']); ?>
【 確認 】
$_SESSION の トークンと
$_POST のトークンを
var_dumpで見てましょう。
<入力画面>
random_bytesによって、トークンが生成されます。
![](https://assets.st-note.com/img/1657254009184-xnm8lsv6xe.png?width=1200)
b88a8c87ec2396c31b5de152a15f461d7a49283a573f0b3a9bbbeecb9585def9 この英数字です。
項目を入力して「確認する」ボタンをクリック・・・
![](https://assets.st-note.com/img/1657254714797-QREa23PmZn.png?width=1200)
< 確認画面 >
var_dumpで、$_POSTの連想配列の中身が表示されます。
氏名、メールアドレスの入力内容が、バリューとして表示されます。
最下部に、トークンが表示されています。
![](https://assets.st-note.com/img/1657254761881-mePQBla4Mz.png?width=1200)
b88a8c87ec2396c31b5de152a15f461d7a49283a573f0b3a9bbbeecb9585def9 この英数字です。
入力画面のトークンと一致しています。
<完了画面>
![](https://assets.st-note.com/img/1657254831714-bPs5p7xLDg.png?width=1200)
トークンが一致して、安全が確認されて、送信が完了しました!!
【 まとめ 】
csrfの対策について、学びました。
少し理解が難しい内容でした。
悪意のある者がいなければ、こんな難しいことを学ぶ必要もないのですが。。。
仕方ありませんね〜。。。