質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.77%

PHPメールフォールのXSSとCSRF対策について

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 4
  • VIEW 8,142
退会済みユーザー

退会済みユーザー

皆様から回答を受けて


皆様回答ありがとうございました。
まだ調べている途中ですが、この質問を見た方のために参考にしているサイトと書籍をシェアしたいと思います。

 メールフォーム作成の参考 
- [書籍]PHP逆引きレシピ 第2版
※第7章の551頁に「メール送信フォームを作成したい」の記述があります。
 質問した構成と同じく入力→確認→送信と遷移するメールフォームです。
 メールフォームを作成したい方はこの書籍を参考にするとよろしいかと存じます。

 サニタイズやバリデーション関連 
- [Qiita]$_GET, $_POSTなどを受け取る際の処理
- [Qiita」PHPで各種バリデーション

 ※jQueryを利用して入力等のチェックを行っていても、HTTPリクエストは偽装できるため、PHP側でも入力チェック(バリデーション)を行う必要があります。jQueryでの入力チェックは、ユーザビリティ向上の役割しかありません。 
入力処理に関しては、上記のサイトもしくは、セキュリティ分野の大家である徳丸氏の著書(体系的に学ぶ 安全なWebアプリケーションの作り方)が参考になるかと思います。

 セキュリティ全般 
- [YouTube]安全なPHPアプリケーションの作り方2014 〜必要最低限のセキュリティ対策はこれだ〜 - PHPカンファレンス2014 (メイントラック)

- [書籍]PHP逆引きレシピ 第2版
※第10章にセキュリティに関する記述がありますが、初心者の私でも説明が分かりやすいです。

- [書籍]パーフェクトPHP (PERFECT SERIES 3) 
※パーフェクトPHPの書籍の9章にセキュリティ関する記述があります。
 書籍のCSRFへの対応方法として「ワンタイムトークンによるチェック」との記述がありますが、
 徳丸氏から指摘があります。詳細は下記をご覧ください。
 CSRF対策のトークンをワンタイムにしたら意図に反して脆弱になった実装例

 補足 
なんと徳丸氏がPHPの書籍に関して、SQLエスケープの解説状況を記載しております。
書籍別にレビューとセキュリティの状況が記載されているので非常にありがたいです。
本件とはあまり関係ありませんが、書籍を選ぶときの参考にもなります。
- [ブログ]PHPとセキュリティの解説書12種類を読んでSQLエスケープの解説状況を調べてみた


【注意】質問で記述したコードには脆弱性があります。絶対にコピペしないでください。
--------------- ここからが最初に投稿した質問内容です ---------------

はじめに

お世話になります。
現在PHPの勉強をしながらセキュアなメールフォームを作成しております。
「入力画面」→「確認画面」→「送信画面」と遷移するメールフォームの作成をしております。
作成したコードを記載しますので、XSSやCSRFなどのセキュリティ面で問題があるか教えてください。

XSS対策のためにhtmlspecialcharsを使用して特殊文字はエスケープしております。
CSRF対策としてopenssl_random_pseudo_bytesでトークンを作成してチェックしております。

入力画面

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>PHP送信フォームテスト(入力)</title>
</head>
<body>
<form method="post" action="check.php">
氏名:<input type="text" name="name">
メールアドレス:<input type="text" name="mail">
<input type="submit" value="送信確認">
</form>
</body>
</html>

確認画面(check.php)

<?php
session_start();
// HTML特殊文字をエスケープする関数
function h($str) {
    return htmlspecialchars($str,ENT_QUOTES,'UTF-8');
}
// CSRFトークンを作成
$TOKEN_LENGTH = 16;
$bytes = openssl_random_pseudo_bytes($TOKEN_LENGTH);
$_SESSION["TOKEN"] = bin2hex($bytes);
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>PHP送信フォームテスト(確認)</title>
</head>
<body>
<?php
foreach($_POST as $key => $value){
    $$key = $value;
}
// 値のチェック
$errFlg = 0;
if($name === "") {
    echo "氏名を入力してください。";
    $errFlg = 1;
}
if($mail === "") {
    echo "メールアドレスを入力してください。";
    $errFlg = 1;
}
if($errFlg === 1) {
    echo "<input type='button' value='入力画面へ戻る' onClick='history.back()'></body></html>";
    exit();
}
?>
<form method="post" action="fin.php">
氏名:<?php echo h($name); ?>
メールアドレス:<?php echo h($mail); ?>
<input type="submit" value="送信">
<input type="hidden" name="token" value="<?php echo h($_SESSION["TOKEN"]); ?>">
<input type="hidden" name="name" value="<?php echo h($name); ?>">
<input type="hidden" name="mail" value="<?php echo h($mail); ?>">
</form>
</body>
</html>

送信画面(fin.php)

<?php
session_start();
mb_language("ja");
mb_internal_encoding("UTF-8");
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
    <title>PHP送信フォームテスト(送信)</title>
</head>
<body>
<?php
foreach($_POST as $key => $value){
    $$key = $value;
}
// tokenのチェック
if($_SESSION["TOKEN"] !== $_POST["token"]) {
    echo "不正です。</body></html>";
    exit();
}
$to = "hoge@hoge.jp";
$subject = mb_convert_encoding("メールフォームからの送信","UTF-8");
$mailbody = mb_convert_encoding("氏名:$name\nメールアドレス:$mail","UTF-8");

if($name !== "" && $mail !== "") {
    if(mb_send_mail($to,$subject,$mailbody,"From:$mail")) {
        echo "メール送信しました。";
    } else {
        echo "メール送信に失敗しました。";
    }
} else {
    echo "入力されていません。";
}
?>
</body>
</html>
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+3

細かく見ていませんが、致命的そうなところが1点。

foreach($_POST as $key => $value){
    $$key = $value;
}

例えばこのスクリプトに対して、

<?php
session_start();

foreach($_POST as $key => $value){
    $$key = $value;
}

var_dump($_POST);
var_dump($_SESSION);

if ($_POST) {
    if($_SESSION["TOKEN"] !== $_POST["token"]) {
        echo "invalid token !!!";
        exit();
    }
    echo "token OK";
}

このようなフォームから POST すると・・・

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title></title>
</head>
<body>
    <form method="post" action="z.php">
        <input type="hidden" name="_SESSION[TOKEN]" value="oreore">
        <input type="hidden" name="token" value="oreore">
        <input type="submit" value="SEND">
    </form>
</body>
</html>

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2014/11/27 15:12

    TaMaMhyu さん
    コメントありがとうございます。
    入力フィルタリングする必要があるんですね。
    調べてみたらfilter_input関数で出来そうな気がします。
    $$の展開とフィルタリングについてもう少し調べてみます。
    勉強になりました。ありがとうございます。

    キャンセル

  • 2014/11/27 17:06

    もしかしたら既に読まれているかもしれませんが一応貼っておきます
    http://qiita.com/mpyw/items/2f9955db1c02eeef43ea

    キャンセル

  • 2014/11/27 17:41

    CertaiN さん
    参考URLありがとうございます。
    mpywさんですよね。いつもQiitaで記事を拝見しております。
    参考URLを読んで勉強します。
    ありがとうございます。

    キャンセル

+2

foreach($_POST as $key => $value) { $$key = $value; }
については、既に指摘がありますが、古いPHPにあったregister_globals = onと同じ効果なので、害悪については「register_globals」でググルと良いでしょう。

他の脆弱性としては、
if(mb_send_mail($to,$subject,$mailbody,"From:$mail")) {
で、$mail の内容のバリデーションを行っていないので、改行を含めることでメールに任意のヘッダのインジェクションが可能。
つまり、任意のアドレスにメールを送るなどができます。

細かいところでは、POSTデータにmailやnameが無かった場合、変数未定義エラーになります。
これはおそらく、最初のPOSTデータ取り出しのところの改善の中で対処するのでしょう。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2014/11/28 12:16

    otn さん
    ご回答ありがとうございます。
    調べ方まで記載頂き助かります。
    ご指摘頂いた箇所を調べて勉強します。
    取り急ぎ、お礼申し上げます。

    キャンセル

  • 2014/12/01 22:40

    横から失礼します。_SESSION["TOKEN"] と $_POST["token"] がともに指定されていない場合にも_SESSION["TOKEN"] === $_POST["token"] が成立するので、CSRF脆弱性になりますよ。

    キャンセル

  • 2014/12/02 13:53

    ockeghem さん
    徳丸さんですよね。コメントありがとうございます。
    確かに指定されていない場合でも成立してしまいますね。
    徳丸本を読んで勉強します。
    ご指摘ありがとうございました。

    キャンセル

+1

プログラム的な所は、識者の皆様がお答え下さっているので
別の方面から。
私は自作のプログラムを、N-Stalkerのフリー版で検査しています。
http://www.nstalker.com/
フリーながら、なかなかきちんと見てくれるので重宝しています。
惜しむらくは全部英語って事ですが、まぁ見れば判るレベルかと。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2014/12/02 14:06

    tsuntsun さん
    回答ありがとうございます。
    プログラムを検査できるツールがあるんですね。はじめして知りました。
    後ほど、ご紹介頂いたツールを確認してみます。
    ありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.77%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る