お問い合わせフォームのセキュリティ対策【PHPによるCSRF対策の実装】

1. お問い合わせフォームのセキュリティ対策とは

 お問い合わせフォーム(図1-1)より入力した情報(お名前、メールアドレス、題名、お問い合わせ内容)は、メール送信ボタンを押下することにより、同一ドメインにあるPHPのメール送信プログラムに引き渡され処理されます。

 

  
図1-1 自サイトのお問い合わせフォーム

 

 ただ、メール送信プログラムへのデータの引き渡しは、同一ドメインのお問い合わせフォームからではなく、やろうと思えば、他のドメインからでもできてしまいます。

 このような、他のドメインから自サイトの処理プログラムにデータを送信して処理させるといった攻撃手法をクロスサイトリクエストフォージェリ(CSRF)といいます。

 この記事では、お問い合わせフォームおよびメール送信プログラムにCSRF対策を実装する方法について説明します。

 

 お問い合わせフォームの実装方法については、以下の記事をご覧ください。以下の記事では、固定のトークン認証により、特定フォームからの要求であることをチェックしています。しかし、セキュリティ的にはザルですので、本記事の対策を組み込むことで、お問い合わせフォームのセキュリティ強化につながると考えます。

静的サイトにお問い合わせフォームを実装する【メール送信機能】
サイトにお問い合わせフォームを実装する方法と、実際にお問い合わせメールを送信する機能について説明します。

 

2. CSRF対策の考えと行うべきこと

 CSRF対策を行うにあたり、お問い合わせフォームおよびメール送信プログラムに要求されるのは以下の2点です。

(1)メール送信プログラムは自サイトのお問い合わせフォームからの要求以外を受け付けないこと
(2)なりすましによるメール送信プログラムへの要求をブロックするしくみであること
 
 
 上記のうち、要求(1)は固定のトークン認証でも可能ではあるのですが、なりすまし阻止を含む完全なCSRF対策を行うためには、加えて要求(2)を実現することが必要です。そのために、行うべきことは以下の4点となります。
 

①自サイトのお問い合わせフォームページで、ランダムな文字列からなるトークンを発行する
②フォームに入力した内容が送信される際には①で発行したトークンもセットで送信されるようにする
③メール送信プログラムはトークンが自サイトで発行されたものかどうかをチェックする
④自サイトで発行されたトークンであればメール送信を行う

 

3. お問い合わせフォームのCSRF対策

 前回の記事では、お問い合わせフォームページを「toiawase.html」とHTMLファイルとして作成していましたが、これにPHPのソースコードを組み込むため、「toiawase.php」というPHPファイルに変更しました。

 CSRF対策のため追加した部分の説明(コード内番号)は、以下のとおりです。

①セッションを開始:session_start()関数をコール
②文字コードを指定:明示的に文字コード(utf-8)を指定
③クリックジャッキング対策:フレーム内のページ表示を同一ドメイン内のみ許可する指定
④疑似乱数のバイト文字列生成:openssl_random_pseudo_byte(16)関数により生成
⑤バイナリのデータを16進表現に変換:bin2hex()関数による変換
⑥生成したランダムな文字列をセッション変数に設定:$_SESSION[‘csrf_token’] に保存
⑦生成したランダムな文字列をトークン文字列に設定:入力データとして”php/mailer.php”に送る
 
<?php
//セッションを開始       ・・・①
session_start();
//文字コード指定         ・・・②
header("Content-type: text/html; charset=utf-8");
//クリックジャッキング対策   ・・・③
header('X-FRAME-OPTIONS: SAMEORIGIN');

//疑似乱数のバイト文字列(16バイト)を生成     ・・・④
$token_byte = openssl_random_pseudo_bytes(16);
//バイナリのデータを16進表現に変換          ・・・⑤
$csrf_token = bin2hex($token_byte);
//セッション変数設定                       ・・・⑥
$_SESSION['csrf_token'] = $csrf_token;
?>

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <script type="text/javascript" charset="UTF-8"></script>
    <title>お問い合わせフォーム</title>
    <link rel="stylesheet" href="css/layout.css">
    <script src="//code.jquery.com/jquery-2.2.4.min.js"></script>
    <script>
    //共通パーツ読み込み
    $(function() {
        $("#header").load("common/header.html");
        $("#sidebar").load("common/sidebar-non.html");
        $("#footer").load("common/footer.html");
    });
    </script>
</head>
<body background=image/sozai/hh0430.gif>
<!-- HEADER  -->
<div id="header"></div>
<!-- MAIN CONTENTS  -->
<div id="container">
<div id="content">
  <div id="inner-content" class="clearfix">
    <main id="main">
      <br/>
      <form method="POST" action="php/mailer.php">
        <input type="hidden" name="csrf_token" value="<?=$csrf_token?>">     ・・・⑦
        <div class="form">
          <p><font size="4"><b>お名前</b></font></p>
          <input type="text" name="name" />
          <p><font size="4"><b>メールアドレス</b></font></p>
          <input type="text" name="email" />
          <p><font size="4"><b>題名</b></font></p>
          <input type="text" name="subject" />
          <p><font size="4"><b>お問い合わせ内容</b></font></p>
          <textarea name="message"></textarea><br>
        </div>
        <br/>
        <div class="contact-submit">
          <input type="submit" value="送信">
        </div>
      </form>
    </main>
    <!-- SIDEBAR -->
    <div id="sidebar"></div>
  </div><!-- /#inner-content -->
</div><!-- /#content -->
<br/><br/>
<!-- FOOTER -->
<div id="footer"></div>
</body>
</html>
 
 

4. メール送信プログラムのCSRF対策

 コード内番号の説明は、以下のとおりです。ポイントはメール送信プログラム側でもsession_start()関数をコールする点と、送られてきたトークン文字列とセッション変数に保存してあるトークン文字列とを比較するという点です。

 これにより、なりすましによるメール送信プログラムへの要求をブロックすることができます。

①セッションを開始:メール送信プログラム側でも、session_start()関数をコール
お問い合わせフォームから送られたトークン文字列セッション変数に保存されているトークン文字列とを比較
 一致 :メール送信処理を継続
 不一致:メール送信処理を中止


 
<?php
//セッションを開始            ・・・①
session_start();
// パラメータ取得
$request_param = $_POST;
// お問い合わせ日時
$request_datetime = date("Y年m月d日 H時i分s秒");
 
//自動返信メール
$mailto = $request_param['email'];
$to = '自身のメールアドレス'; //ここを入力
$mailfrom = "From:自身のメールアドレス"; //ここを入力

//問い合わせ相手への送信メール
$subject1 = "お問い合わせ有難うございます。";
$content = "";
$content .= $request_param['name']. "様\r\n";
$content .= "お問い合わせ有難うございます。\r\n";
$content .= "お問い合わせ内容は下記通りでございます。\r\n";
$content .= "=================================\r\n";
$content .= "お名前	      " . htmlspecialchars($request_param['name'])."\r\n";
$content .= "メールアドレス   " . htmlspecialchars($request_param['email'])."\r\n";
$content .= "題名   " . htmlspecialchars($request_param['subject'])."\r\n";
$content .= "内容   " . htmlspecialchars($request_param['message'])."\r\n";
$content .= "お問い合わせ日時   " . $request_datetime."\r\n";
$content .= "=================================\r\n";
 
//管理者確認用メール
$subject2 = "お問い合わせがありました。";
$content2 = "";
$content2 .= "お問い合わせがありました。\r\n";
$content2 .= "お問い合わせ内容は下記通りです。\r\n";
$content2 .= "=================================\r\n";
$content2 .= "お名前	      " . htmlspecialchars($request_param['name'])."\r\n";
$content2 .= "メールアドレス   " . htmlspecialchars($request_param['email'])."\r\n";
$content2 .= "題名   " . htmlspecialchars($request_param['subject'])."\r\n";
$content2 .= "内容   " . htmlspecialchars($request_param['message'])."\r\n";
$content2 .= "お問い合わせ日時   " . $request_datetime."\r\n";
$content2 .= "================================="."\r\n";
 
mb_language("ja");
mb_internal_encoding("UTF-8");
//mail 送信
if (isset($_POST["csrf_token"])                            ・・・②
 && $_POST["csrf_token"] === $_SESSION['csrf_token']) {    ・・・②
  if( filter_var( $mailto, FILTER_VALIDATE_EMAIL ) ){
    if(mb_send_mail($to, $subject2, $content2, $mailfrom)){
      mb_send_mail($mailto,$subject1,$content,$mailfrom);
      ?>
      <script>
         window.location = '../mail-send-end.html';
      </script>
      <?php
    } else {
      header('Content-Type: text/html; charset=UTF-8');
      echo "メールの送信に失敗しました";
    };
  } else {
    header('Content-Type: text/html; charset=UTF-8');
    echo "メールアドレスの形式が正しくありません";
  };
} else {
  echo "メールの送信に失敗しました(トークンエラー)";
}
 
?>
 
 

5. まとめ

 今回の記事では、自サイトに実装したお問い合わせフォームを例にとって、CSRF対策を実装する方法について説明をさせていただきました。CSRF対策の必要性と実装する流れについて、おわかりいただけたと思います。

 お問い合わせフォームは、実際にサイト閲覧者に対しメールを送信することになりますので、悪用されることがあってはなりません。セキュリティ強化のためにCSRF対策を検討されている方に、この記事がすこしでも参考となれば幸いです。

 
 自サイトカスタマイズの全体の概要については以下の記事をご覧ください。
古いウェブサイトを現代風デザインにカスタマイズ【HTML部品化とスマホ対応】
2000年代初頭に作成した昔風の静的サイトに対し、現代風の外観、機能となるようにカスタマイズした記録です。
 
 
おわり
 

コメント

タイトルとURLをコピーしました