PHPで作るチャット(7) クロスサイトスクリプティング(XSS)の修正

2017年12月9日(更新: 2017年12月24日)

前回はデータの多重送信の問題を修正しました。

今回はチャットにあるセキュリティの問題を修正していきます。

セキュリティの潜在的な問題

一見、チャットとして問題が無いように見えますが、現在の状態ではユーザーがHTMLタグやスクリプトをページに埋め込むことができてしまいます。

例えば以下のように、HTMLタグを利用して文字を非常に大きく表示するということができます。

クロスサイトスクリプティングによるHTMLタグの挿入

これはクロスサイトスクリプティングXSS)と呼ばれるセキュリティの問題です。

単純に文字の装飾をするだけのタグであれば大きな問題ありませんが、悪意のあるユーザーが他のユーザーに悪影響を与えるスクリプトを実行させることも可能であり危険です。

クロスサイトスクリプティングの修正

クロスサイトスクリプティングを防ぐためには、入力されたHTMLタグを「タグではないただの文字列」とPHP側で解釈させます。

この処理を行ってくれる関数がPHPには初めから用意されています。その関数は htmlspecialchars です。

PHP: htmlspecialchars – Manual

前回作ったチャットのスクリプトに実際にこれを使った例を以下に記載します。htmlspecialchars を加えたのは、ログファイルから読み込んだテキストを名前と本文に分けた後の部分です。

<!-- chat.php -->

<?php
    // データを書き込むファイルの名前
    $LOG_FILE_NAME = "log.txt";

    // 区切りのための文字列
    $SPLIT = "|-|";

    // 名前を格納する変数
    $name = "noname";
    // メッセージを格納する変数
    $message = "";

    // 送信された名前とメッセージを変数に代入
    if (isset($_POST['name'])) {
      $name = $_POST['name'];

      if (strpos($name, $SPLIT) !== false) {
        // 名前に区切り文字が含まれている場合の処理
        echo "使用できない文字列「|-|」が含まれています。";
        return;
      }

      if ($name == "") {
        $name = "noname";
      }
    }
    if (isset($_POST['message'])) {
      $message = $_POST['message'];

      if (strpos($message, $SPLIT) !== false) {
        // メッセージに区切り文字が含まれている場合の処理
        echo "使用できない文字列「|-|」が含まれています。";
        return;
      }

      //書き込みモードでファイルを開く
      $fp = fopen($LOG_FILE_NAME, "a") or exit($LOG_FILE_NAME . "が開けません");

      // | を区切り文字として2つのデータを繋げて書き込む
      fwrite($fp, $name . $SPLIT . $message . "\n");

      // リダイレクトのためのHTTPヘッダーを送信
      header("Location: " . $_SERVER['PHP_SELF'], true, 303);
    }


    if (!file_exists($LOG_FILE_NAME)) {
      // ファイルがない場合
      echo "書き込みはありません。";

      $linesNum = 0;
    } else {

      // ファイルの全行を読み取る
      $lines = file($LOG_FILE_NAME);

      // 読み込んだ行数
      $linesNum = count($lines);
    }
?>

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>1行メッセージ</title>
</head>

<body>

  <h1>1行メッセージ</h1>

  <form method="post" action="chat.php">
    <div>
      <b>おなまえ</b>
      <input name="name" type="text" size="20" maxlength="10">
    </div>
    <div>
      <b>コメント</b>
      <input name="message" type="text" size="100" maxlength="50" required>
    </div>
    <button name="submit" type="submit">送信</button>
  </form>

  <section>
    <?php

      // 一行ずつ表示する処理
      for ($i = 0; $i < $linesNum; $i++) {

         // 区切り文字でデータを区切って配列に格納
         $array = explode($SPLIT, $lines[$i]);

         // 区切り文字の前の部分は名前
         $name = htmlspecialchars($array[0]);

         // 区切り文字の後の部分はメッセージ
         $message = htmlspecialchars($array[1]);

         // 名前とメッセージを表示
         echo '<p>' . $name . "「" . str_replace(PHP_EOL, "", $message) . '」</p>';
      }
    ?>
  </section>

</body>
</html>

これによって、先程大きなテキストを表示していたタグがただの文字列として認識され、HTMLタグとしての効果は無効化されます。

htmlspecialcharsを使用してタグを無効化するXSS対策

これで、意図しないタグやスクリプトを埋め込まれる心配はなくなりました。

PHPで作るチャット(7) クロスサイトスクリプティング(XSS)の修正」への1件のフィードバック

  1. ピンバック: PHPで作るチャット(6) データ送信に関する問題の修正 - JoyPlotドキュメント

コメントを残す

メールアドレスが公開されることはありません。