receive.php

<!DOCTYPE html><!DOCTYPE html><html lang="ja"><head> <meta charset="UTF-8"> <title>データ確認</title></head><body>

<?php // エラーを格納するための変数 $err = '';
// POSTデータを受信したら if($_SERVER['REQUEST_METHOD']=='POST'){
// 受信データをエスケープ $myname = htmlspecialchars($_POST['myname'], ENT_QUOTES,'UTF-8'); $age = (int)$_POST['age'];
// 名前の入力チェック if($myname){ // 名前がが入力されていたら // 文字数チェック if(strlen($myname) > 30){$err='ユーザー名が長いです';} }else{ // 名前の入力が空だった場合 $err = '名前の入力がありません'; }
// 年齢の入力チェック // 半角数字のチェック if(preg_match('/^[0-9]+$/', $age)){ // 200歳以上はエラーとみなす if($age > 200){ $err = '正しい年齢を入力ください'; } }else{ $err = '年齢は半角数字を入れてください';  }
}else{ $err = '正常にデータを受信できませんでした'; $myname = ''; $age = ''; }
// 文字の入力チェックを行い、エラーの有り無しを判定し、 // 確認画面か、再度フォームを出力するかを切り分け if($err){ // エラーの場合 echo 'エラー:'.$err; echo '<form action="receive.php" method="post">'; echo '名前:<input type="text" name="myname" value="'.$myname.'"><br>'; echo '年齢:<input type="number" name="age" value="'.$age.'"><br>'; echo '<input type="submit" value="データを送信">'; echo '</form>';
}else{  // エラーが無かった場合 // セッションにデータを格納する session_start(); $_SESSION['myname'] = $myname; $_SESSION['age'] = $age;
// Tokenを生成。Tokenを利用することで、CSRF対策となる // 疑似乱数を生成 $bytes = openssl_random_pseudo_bytes(16); // 16進数に変換 $token = bin2hex($bytes);
//echo 'トークン:'.$token; // セッションにセット $_SESSION['token'] = $token; ?> <h1>入力データの確認</h1> 名前:<?php echo $myname ?><br> 年齢:<?php echo $age ?><br> <form action="tnk.php" method="post"> <input type="hidden" name="token" value="<?php print $token ?>"> <input type="submit" value="データ送信"> </form>

<?php }
?>
</body></html>

ink.php

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ありがとうございました</title>
</head>
<body>

<?php
// セッションスタート
session_start();

// トークンを比較し、一致しなければエラー出力
if($_POST['token'] != $_SESSION['token']){
echo '<h1>エラー:ページ遷移が正しくありません</h1>';
}else{

$myname = htmlspecialchars($_SESSION['myname'], ENT_QUOTES,'UTF-8');
echo '<h1>'.$_SESSION['myname'].'様ありがとうございました</h1>';


// 最終的に、セッションを破壊する
session_destroy();

}
?>
</body>
</html>

感想

シンプルなシステムで、最初はかなり簡単そうだと思ったが、正規表現やセキュリティ対策にまで触れられていて意外に濃い内容だった。

システムを完成させることで達成感を味わうことは出来たが、ただ言われた通りに打っているだけになってしまいがちなきもした。

今回は大丈夫だったが、もしどこかで引っかかってしまった時に聞ける人がいないのはかなり辛いと思った

補足

[補足資料]
schooの生放送でお伝えしきれなかった箇所を補足させていただきます。


① セッションの破棄について

授業でセッションの破棄について「session_destroy()」と行うと
紹介させていただきました。

この「session_destory()」だけでは完全にセッション情報を
削除するのには不十分でした。完全にセッション情報を削除するには
下記コードが必要です。

// セッション変数を全て解除する
$_SESSION = array();

// セッションを切断するにはセッションクッキーも削除する。
// Note: セッション情報だけでなくセッションを破壊する。
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}

// 最終的に、セッションを破壊する
session_destroy();


トークンの比較について

授業中にトークンの比較を下記コードで比較を行いました。
「if($_POST['token'] != $_SESSION['token']){}」

こちらのコードでは正常にページ遷移を行った場合は正しく動作しますが、
最後のthk.phpに直接アクセスした場合、POSTデータのトークン、
SESSIONデータのトークンが共に、空となりますので、一致しプログラム上
正常と判断されてしまいました。

こちらのコードをトークン情報がセットされているかを確認し、無ければエラー
コードをトークン用の変数に代入しています。
その後にトークンの比較を行うよう修正いたしました。

// POSTデータのトークンを確認
if(isset($_POST['token'])){$post_token = $_POST['token'];}
else{$post_token = 'err123';}

// SESSIONデータのトークンを確認
if(isset($_SESSION['token'])){$session_token = $_SESSION['token'];}
else{$session_token='err456';}

// トークンデータの比較
if($post_token != $session_token || $_SERVER['REQUEST_METHOD']!='POST'){}

発表用メモ

formタグ

type属性
text,submit

name属性
フィールドに名前をつける

action属性
フォームの送信ボタンを押して送信されるデータの送信先を指定する

method属性
データを送る方式
POSTもしくはGET

データの送信方法
input type=submitをクリックするとreceive.phpにデータを送信することができる

データを受信する
<?php $_POST['myname'] ?>

クロスサイトスクリプティング対策
クロスサイトスクリプティング:Webアプリケーションの脆弱性[1]もしくはそれを利用した攻撃
htmlspecialchars関数でエスケープ処理を行う
htmlspecialchars($_POST['myname'], ENT_QUOTES,'UTF-8');

(int)で数字に変換される
(int)$_POST['age'];

名前の入力は必須とする
名前が入力されていなかったら、名前の入力がありませんと表示するようにする。

preg_matchという関数を利用して正規表現を利用する。
何文字かの0−9までの文字ならOK
200以上の数字が入力されたらエラーになる。

strlen:文字数をチェックする関数
1文字が3バイトなので、10文字以上をエラーとしたい場合は30とする。

エラーがあれば再度フォームを表示させる。
エラーがなければ確認画面を出す。
if($err):エラーが存在すれば

sessionによってデータがサーバーに保存される。
sessionによってデータをサーバーに送ったり、サーバーから受け取ったりできる。

value属性によって次のページに行っても値を入れておくことができる。
value="'.$age.'"

Tokenを使用して、正しいユーザーかどうかを確認
確認ページでTokenを作成
SessionとFormのhiddenでTokenをセットする。
<input type="hidden" name="token" value="<?php print $token ?>">
ありがとうございましたページでSessionとformのトークンが合致するか確かめる。

 

thk.php

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ありがとうございました</title>
</head>
<body>

<?php
// セッションスタート
session_start();
// POSTデータのトークンを確認
if(isset($_POST['token'])){$post_token = $_POST['token'];}
else{$post_token = 'err123';}

// SESSIONデータのトークンを確認
if(isset($_SESSION['token'])){$session_token = $_SESSION['token'];}
else{$session_token='err456';}

// トークンを比較し、一致しなければエラー出力
if($post_token != $session_token || $_SERVER['REQUEST_METHOD']!='POST'){
echo '<h1>エラー:ページ遷移が正しくありません</h1>';
}else{

$myname = htmlspecialchars($_SESSION['myname'], ENT_QUOTES,'UTF-8');
echo '<h1>'.$myname.'様ありがとうございました</h1>';


// セッション情報を削除する
// セッション変数を全て解除する
$_SESSION = array();

// セッションを切断するにはセッションクッキーも削除する。
// Note: セッション情報だけでなくセッションを破壊する。
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}

// 最終的に、セッションを破壊する
session_destroy();

}
?>
</body>
</html>