見出し画像

【 PHP学習 #18 】 セキュリティ 「CSRF」の対策  FIREへの旅路 ♯468

セキュリティ対策を学びます。

今回は、CSRF という攻撃です。


【 CSRF 】  Cross-Site Request Forgeries

クロスサイトリクエストフォージェリ は、Webアプリケーションの脆弱性の一つもしくはそれを利用した攻撃。略称はCSRF、またはXSRF。リクエスト強要、セッションライディング とも呼ばれる。1990年代はイメタグ攻撃とも呼ばれていた

ウィキペディア(Wikipedia)

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 ) )

これで数字と、アルファベットに変換されます。

トークン

このトークンを、変数に格納します。

$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によって、トークンが生成されます。

入力画面

b88a8c87ec2396c31b5de152a15f461d7a49283a573f0b3a9bbbeecb9585def9 この英数字です。


項目を入力して「確認する」ボタンをクリック・・・


< 確認画面 >

var_dumpで、$_POSTの連想配列の中身が表示されます。

氏名、メールアドレスの入力内容が、バリューとして表示されます。
最下部に、トークンが表示されています。

確認画面

b88a8c87ec2396c31b5de152a15f461d7a49283a573f0b3a9bbbeecb9585def9 この英数字です。

入力画面のトークンと一致しています。


<完了画面>

完了画面

トークンが一致して、安全が確認されて、送信が完了しました!!



【 まとめ 】

csrfの対策について、学びました。

少し理解が難しい内容でした。

悪意のある者がいなければ、こんな難しいことを学ぶ必要もないのですが。。。

仕方ありませんね〜。。。


この記事が気に入ったらサポートをしてみませんか?