みなさん、お問い合わせフォームはどうやって作っていますか?
恐らくCMSのプラグインを使って作成することがほとんどかと思います。
ただ、ときにはスクラッチでお問い合わせフォームを作成しなければならないことがあると思います。
そこで今回はPHPを使用して、セキュリティを考慮したお問い合わせフォームを作成します。
なお、今回のお問い合わせフォームは入力ページと確認ページ、完了ページの3つに分ける想定で進めます。
もとになるHTML
まずはもとになるフォームを静的で作ります。
ということで、出来上がったフォーム部分のHTMLコードがこちらになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | <form action="confirm/" method="post"> <table class="c-table01"> <tbody> <tr> <th> <label class="required" for="name">お名前</label> </th> <td> <input type="text" name="name" id="name" placeholder="例) 山田太郎" required> </td> </tr> <tr> <th> <label class="required" for="kana">ふりがな</label> </th> <td> <input type="text" name="kana" id="kana" placeholder="例) やまだたろう" required> </td> </tr> <tr> <th> <label class="required" for="mailaddress">メールアドレス</label> </th> <td> <input type="text" name="mailaddress" id="mailaddress" placeholder="例) example@example.com" required> </td> </tr> <tr> <th> <label class="required" for="tel">電話番号</label> </th> <td> <input type="text" name="tel" id="tel" placeholder="例) 01233456789" required> </td> </tr> <tr> <th> <label class="required">お問い合わせ種別</label> </th> <td class="radio"> <label> <input type="radio" name="type" value="お見積り" checked> お見積り </label> <label> <input type="radio" name="type" value="others"> その他 </label> </td> </tr> <tr> <th> <label class="required" for="detail">お問い合わせ内容</label> </th> <td> <textarea name="detail" id="detail" required></textarea> </td> </tr> </tbody> </table> <button type="submit" class="c-btn01">入力内容の確認</button> </form> |
スタイルをいい感じに当てるとこのようになりました。
それではここからPHPを用いてフォームの構築を行います。
2重送信の防止
まずは2重送信の防止についてです。
概要
フォーム送信後にページをリロードした際や送信ボタンをダブルクリックした際に入力内容が再度送信されるのを防ぐため、セッションを用いて対策を行います。
セッションを用いた2重送信対策は以下の通りです。
- 1トークンを生成
- 2トークンを次のページにPOST送信
- 3前のページから送られてきたトークンとセッション変数のトークンが一致するか判定
- 4トークンが一致するときはフォームのフローに沿って処理、一致しないときは入力画面に飛ばしてエラー表示
実装
それでは入力画面、確認画面、完了画面の3つに分けて処理を解説していきます。
入力画面での処理
入力画面で必要な処理は、トークンの生成と確認画面へのPOST送信です。
1 2 3 4 5 6 7 8 9 10 | <?php $_SESSION = array(); //セッション変数の初期化 session_start(); //セッションの開始 $token = uniqid('', true); //トークンの生成 $_SESSION['token'] = $token; //セッション変数にトークンをセット ?> |
処理の内容としては、コメントの通りですがセッションを開始し、トークンを生成したうえでセッション変数に生成したトークンをセットするというものです。
そして、下記のようにフォームタグ内で入力内容と併せてトークンもPOST送信します。
1 2 3 4 5 6 7 8 9 | ・・・ <form action="confirm/" method="post"> <input type="hidden" name="token" value="<?php echo $token; ?>"> // <- 追加 </form> ・・・ |
また、確認・完了ページで行うトークン判定のところで条件が一致しなかった際にセッション変数を用いてエラー返すようにするため、下記記述を任意の場所に追記します。
1 2 3 4 5 6 7 | <?php if( $_SESSION['error-msg'] ) : ?> //セッション変数にエラーが設定されているとき <p class="error"><?php print $_SESSION['error-msg']; ?></p> <?php unset($_SESSION['error-msg']); //セッション変数内のエラーの削除 ?> <?php endif; ?> |
これでトークンによる判定ができるのですが一点注意が必要なことがあり、PHPのセッションを開始すると一部ブラウザでブラウザバック時にフォームの入力内容が消えてしまうことがあります。
これはPHPのセッションを開始すると、クライアントキャッシュが無効になるHTTPヘッダが出力されることが原因です。
そのため、下記の1行をセッション開始する前に追加することで対策します。
1 2 3 | session_cache_limiter('none'); //不要なHTTPヘッダを出力しない |
これで入力画面で必要な処理は以上です。
続いて、確認画面での処理です。
確認画面での処理
確認画面で必要な処理は、トークンの判定と送信です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <?php session_cache_limiter('none'); //不要なHTTPヘッダを出力しない session_start(); //セッション開始 if( isset($_POST['token']) ) { //POSTされたトークンを変数にセット $token = $_POST['token']; } if( isset($_SESSION['token']) ) { //セッション変数のトークンを変数にセット $session_token = $_SESSION['token']; } unset($_SESSION['token']); //セッション変数のトークンを削除 if ( ( empty($token) || $token != $session_token) ) { //トークンの判定 $_SESSION['error-msg'] = '不正なリクエストです。'; //セッション変数にエラーメッセージを設定 header('Location:' /contact/' ); //フォームのTOPページに遷移 exit; } $_SESSION['token'] = $token; //セッション変数にトークンをセット ・・・ ?> |
処理の内容としては、18行目から23行目でPOSTされたトークンとセッション変数内のトークンが一致するかどうかを判定します。
POSTされるはずのトークンが空のときやPOSTされたトークンがセッショントークンと一致しないときには、不正なリクエストとしてフォームのTOPにエラーメッセージと一緒に送り返します。
POSTされたトークンとセッショントークンが一致するときには再度セッション変数にトークンをセットし、下記のように入力画面のときと同様にフォームタグ内で入力内容と併せてトークンもPOST送信します。
1 2 3 4 5 6 7 8 9 | ・・・ <form action="confirm/" method="post"> <input type="hidden" name="token" value="<?php echo $token; ?>"> // <- 追加 </form> ・・・ |
これで確認画面の処理まで完了しました。
最後に完了画面での処理です。
完了画面での処理
完了画面で必要な処理は、トークンの判定です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <?php session_cache_limiter('none'); //不要なHTTPヘッダを出力しない session_start(); //セッション開始 if( isset($_POST['token']) ) { //POSTされたトークンを変数にセット $token = $_POST['token']; } if( isset($_SESSION['token']) ) { //セッション変数のトークンを変数にセット $session_token = $_SESSION['token']; } unset($_SESSION['token']); //セッション変数のトークンを削除 if ( ( empty($token) || $token != $session_token) ) { //トークンの判定 $_SESSION['error-msg'] = '不正なリクエストです。'; //セッション変数にエラーメッセージを設定 header('Location:' /contact/' ); //フォームのTOPに遷移 exit; } ・・・ ?> |
処理の内容は確認画面とほぼ同じになりますがPOSTされたトークンとセッション変数内のトークンが一致するかどうかを判定します。
もし、POSTされるはずのトークンが空のときやPOSTされたトークンがセッショントークンと一致しないときは、不正なリクエストとしてフォームのTOPにエラーメッセージと一緒に送り返します。
POSTされたトークンとセッショントークンが一致するときにはメール送信等の処理が続きます。
これで2重送信対策ができましたので実際に確認してみましょう。
動作確認
フォーム送信完了後にブラウザバックで確認画面に戻り、再度送信ボタンを押します。
すると、下記画像のようにフォームのTOPに遷移し、エラーが表示されたかと思います。
これで2重送信対策の完了です。
クロスサイトスクリプティング
次にクロスサイトスクリプティングの対策についてです。
概要
クロスサイトスクリプティングとは、フォームに入力した内容で不正な処理を発生させるというものです。
これはフォームから受け取った値に対して、エスケープ処理を施さずにそのまま処理することが原因です。
例
実際にクロスサイトスクリプティングで不正な処理を発生させてみましょう。
名前の項目にスクリプトを入力してみます。
すると、下記のように次の確認ページ画面でお名前の値がスクリプトと解釈され、アラートが出てしまいました。
これがクロスサイトスクリプティングの一例です。
今回はアラートでしたので特にサイトに影響はありませんが、もしサイトやサーバに影響を与えるコードが入力されたらと考えると恐ろしいですね。
ということで、クロスサイトスクリプティングの恐ろしさをご理解いただけたかと思いますので、対策をしていきましょう。
クロスサイトスクリプティングの対策
対策といってもとても簡単で、フォームから受け取った値を出力する前にエスケープ処理を施すだけです。
エスケープ処理は下記7行目のようにPOSTで送られてきた入力値をhtmlspecialcharsという関数を使用することでエスケープ処理を施し、echoで出力します。
htmlspecialchars関数については別の記事で紹介していますので、詳しくは下記をご覧ください。
1 2 3 4 5 6 7 8 9 10 | <tr> <th> <label class="required" for="name">お名前</label> </th> <td> <?php echo htmlspecialchars( $_POST['name'], ENT_QUOTES, 'UTF-8' ); ?> // <- 追加 </td> </tr> |
これでクロスサイトスクリプティングの対策は問題ないのですが、
この書き方では記述量が増えてしまい、コードが読みづらくなってしまうため下記のようにエスケープ関数をさらに関数化して短縮します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <?php ・・・ function h( $str ) { return htmlspecialchars( $str, ENT_QUOTES, 'UTF-8' ); } ?> ・・・ <tr> <th> <label class="required" for="name">お名前</label> </th> <td> <?php echo h($_POST['name']); ?> </td> </tr> |
これで17行目のようにhという関数を呼び出すだけでエスケープできるようになりました。
では実際にエスケープ処理ができているか確認してみましょう。
先ほどは入力値がスクリプトとして動いてしまっていましたが、今回は問題なく文字列として出力されていますね。
これでクロスサイトスクリプティングの対策が完了です。
この後に完了画面での確認メールの送信等の処理が続き、
メール内に入力値を入れる場合には確認画面の際と同じように入力値をエスケープしてから出力するという流れになります。
まとめ
ということで今回はセキュリティを考慮したお問い合わせフォームを作成しました。
いかがだったでしょうか。
今回はお問い合わせフォームを想定して作りましたが、
お問い合わせフォームに限らずユーザーから値を受け付けるフォームではどんな値が入力されるかわからないため、必ずエスケープ処理を施してから処理を行うようにしましょう。
ユーザーから受け付ける値というのはユーザーが直接入力するテキストボックスに限らず、ラジオボタンやセレクトボックスも同様です。
というのも、ラジオボタンやセレクトボックスは事前にいくつか用意した値の中からユーザーに選んでもらう形式ですが、画像のようにブラウザの検証ツールから値を自由に書き換えることができてしまうからです。
そのため、ユーザー側から受け付ける値は基本的にエスケープ処理するようにしましょう。
以上、最後までご覧いただきありがとうございました~。