最低限舐められないための対策

さてさて、前回は小さな掲示板を作りました。あれくらいの機能であればサニタイズで十分なセキュリティですが、これからより機能を充実していくにあたり不十分です。

例えば、会員制にすればCSRF攻撃やインジェクション攻撃、セッションハイジャック等をされると困りますから禁止にしたいです。つまり防御の必要があります。

また、ファイル添付機能を追加すると、不正スクリプトを投稿されてサイトが乗っ取られたりする危険性がありますし、そうでなくともサイトが誤動作する原因にもなります。

さらに、サイト内コマンド(ageとかsageとかレスアンカー、あるいはマークダウン)を導入するならば、入力に厳密な検証しなければ破壊や誤動作に繋がりますし、改ざんの危険性もあります。

あるいは、SQLを使おうものならSQLインジェクションを防御しなければいけません。


このように、機能が増えて処理が複雑になるほど対策するべき内容が増えます。最も安全なのはサイトを公開しないことです。

流石に公開しないのは本末転倒ですが、なるべく高度な処理を避けるべきなのです。


具体的な対策

あくまで「学校裏サイト作成ガイド」で、あまり難しいことはしません。掲示板を作る程度の能力が身に着けば良いです。SQLとかオブジェクト指向とはフレームワークは要りません。

HTMLは前回のを使いまわして下さい。一般攻撃手法の一つ、前回のXSSに続いてCSRFの対策です。

<?php
//CSRF対策を追加した掲示板

header('content-type:text/html;charset=UTF-8');

//セッションを使う
session_start();

/*
//リファラチェック

//現在のURL
$here = (empty($_SERVER['HTTPS'])?'http://':'https://').$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
//リファラ
$ref = (isset($_SERVER['HTTP_REFERER'])?$_SERVER['HTTP_REFERER']:'none');
*/


$html_file = '先ほどのHTMLを保存しているパス';

//投稿内容を保存しておくファイルのパス ここではphpと同一ディレクトリに「chat.txt」の名前で保存する。
$chat_file = 'chat.txt';

//投稿したらこの処理をする
if (isset($_POST['name'], $_POST['message'], $_POST['token']) && (isset($_SESSION['token']) && ($_SESSION['token'] === $_POST['token']))){
/*
//リファラチェック
    if ($here !== $ref){
        echo 'リファラチェックで防御しました。';
        exit;
    }
*/
    //特殊文字を変換 メッセージは自動改行も
    $name = htmlspecialchars($_POST['name'], ENT_QUOTES, 'UTF-8');
    $message = nl2br(htmlspecialchars($_POST['message'], ENT_QUOTES, 'UTF-8'), false);
    file_put_contents($chat_file, '<p><b>'.$name.'</b></p><p>'.$message.'</p><hr>', FILE_APPEND | LOCK_EX);

    //「フォーム再送信」を防止するために、リロードする
    header('Location:./');
    exit;
} else {

    //乱数を生成
    $token = base64_encode(random_bytes(random_int(64, 100)));

    //セッションに保存。これがワンタイムパスワードのようなもの
    $_SESSION['token'] = $token;
}

//投稿内容を保存するファイルが存在し、かつ中身があること
if (file_exists($chat_file) && !empty(file_get_contents($chat_file))){

    //ファイルの中身を取得
    $content = file_get_contents($chat_file);
} else {
    $content = '誰も投稿していません。';
}

//投稿内容の下に投稿フォームを追加
$content .= '<form action="" method="POST"><input type="hidden" name="token" value="'.$token.'">
<p><label>名前<input type="text" name="name"></label></p>
<p><label>メッセージ<br><textarea name="message" rows="5"></textarea></label></p>
<p><label><button type="submit">投 稿</button></label></p></form>';

echo str_replace('{CONTENT}', $content, file_get_contents($html_file));

これで、少なくとも外部から自動投稿ツール等を使ってスパム投稿される心配はないでしょう。何故なら、実際にサイトにアクセスしてトークンを取得し、投稿する時にセッションID(通常はクッキーに保存)とトークンを送信してIDに対応するトークンと送信されたトークンが一致しなければ投稿出来ないからです。

これはCSRFのハードルを上げることになり、攻撃が成功する確率を下げられます。(完璧に防げる訳ではない)

CSRFを仕掛けてみましょう。サイトを公開するサーバーを最低限一つは用意して下さい。一つは攻撃対象です。もう一つは、攻撃対象から「Ctrl + s」で保存してきたHTMLファイルの「form action=""」の部分を「form action="攻撃対象のURL"」に書き換えて公開するか、ローカルブラウザで開いて下さい。

では、攻撃対象のURLに書き換えた投稿フォームから送信してみましょう。もしも投稿が成功した場合、CSRFが成功したことになります。失敗すれば、正しく防御出来たことになります。

もしも期待する結果にならない場合は、php.iniで「session.cookie_samesite = "Strict"」となるように書き換えてみましょう。


何をしても駄目な場合、リファラーチェックも追加しましょう。コメントアウトを外すだけです。

ワンタイムトークンとリファラチェックを併せるとより強力な結界になります。どうしたら攻撃出来るか試行錯誤しましょう。

また、万が一攻撃が成功してしまってもサイトが誤動作しないようにする対策も必要です。外部から値を受け取るときは、定義されているか「isset()」やちゃんと入力したか「!empty()」「変数 !== ''」選択肢から選ばれたか「in_array」などを使って、十分に検証しましょう。



次回: 逆SEO対策


前回: 基本




広告