PHP

投稿されたデータをサニタイズする

  1. 最終更新日:
  2. 公開日:

「ひと言掲示板」に投稿されたデータをサニタイズする機能を付けていきます。

この記事のポイント

  • サニタイズは入力データを無害化する大事な機能
  • PHPでは主にhtmlspecialchars関数を使ってサニタイズを行う

不正なメッセージの投稿からシステムを守る

前回は書き込みがあった際に、「表示名」か「ひと言メッセージ」のいずれかが未入力だったらエラーメッセージを表示する機能を作りました。
今回は入力されたデータを無害化する「サニタイズ」という機能を付けていきます。

あまり馴染みがない言葉かもしれないので、サニタイズを行う理由を簡単にご紹介します。
例えば、掲示板で次のようにJavaScriptコードを書いて投稿するとします。

JavaScritpコードを入力する

ここで「書き込み」ボタンを押すと、このコードはそのまま「message.txt」に保存されます。

message.txt

'コード入力してみる','<script>alert("Hello");</script>','2019-03-25 19:46:13'

すると、次のように掲示板を読み込む度に「Hello」のアラートメッセージが表示されてしまいます。

不正なアラートが表示される例

これは掲示板を開いた全ての方がアラートメッセージを目にすることになります。
今回の例は単純なアラート文ですが、やろうと思えば外部サイトへ強制的に遷移することなどもできます。

もう1つ、よくある不正な入力例をご紹介します。
次のように、メッセージ欄に「' (シングルクォーテーション)」が混じっていたとします。

入力に「'」が含まれている例

明らかに怪しい入力ですよね。
こちらをそのまま登録すると、「message.txt」には次のように登録されます。

message.txt

'テスト太郎','不正なコード','シングルクォーテーションを打つとmessage.txtのフォーマットが崩れてしまう','2022-15-22 22:22:22'
'さらに追加で','メッセージを登録することもできる','2023-66-55 22:22:22'','2019-03-25 20:05:42'','2019-03-25 20:08:29'

フォーマットがおかしい状態になっていることが一目瞭然です。
このデータを読み込んで表示すると...

不正なデータを表示

1回の入力で2件分のデータが登録されたようになってしまいます。

前置きが長くなってしまいましたが、以上のような不正な入力を無くすための「入力データを無害化する前処理」を今回作っていきます。

「ひと言掲示板を作る」の概要については「ひと言掲示板を作る」をご覧ください。
デモはこちら

前回までに作成したコードはこちら:Github

サニタイズ機能を作る

PHPでサニタイズを行う場合はhtmlspecialchars関数を使います。
こちらの関数は受け取った値に含まれている記号を「HTMLエンティティ」という形式に変換することでコードの無害化を行います。

PHP コード例

htmlspecialchars( $data, ENT_QUOTES, 'UTF-8');

1つ目のパラメータにはサニタイズしたい値、2つ目のパラメータには変換する文字の指定をあらかじめ定義された変数から指定します。
そして最後の3つ目のパラメータは変換後の値の文字コードを指定しています。

htmlspecialchars関数をサニタイズ用途で使うときは「ENT_QUOTES」を指定することが多いですが、この値は先ほど出てきた「' (シングルクォーテーション)」やよく登場する「" (ダブルクォーテーション)」をHTMLエンティティに変換します。

HTMLエンティティはHTMLに記号を通常の文字として書きたいときに使う表記方法のことです。
例えば「'(シングルクォーテーション)」なら「&apos;」、「"(ダブルクォーテーション)」なら「&quot;」、「<」なら「&lt;」など各記号ごとに定義されています。

htmlspecialchars関数で記号をHTMLエンティティに変換することで、先ほどの「<script>alert("Hello");</script>」のようなJavaScriptのタグも「&lt;script&gt;alert(&quot;Hello&quot;);&lt;/script&gt;」になるため、スクリプトが実行されずに通常のテキストとして扱うことができます。

それでは掲示板にサニタイズ機能を加えていきましょう。
次の赤字のコードを追記してください。

index.php

<?php

---- 省略 ----

// 変数の初期化
$current_date = null;
$data = null;
$file_handle = null;
$split_data = null;
$message = array();
$message_array = array();
$success_message = null;
$error_message = array();
$clean = array();


if( !empty($_POST['btn_submit']) ) {
  
  // 表示名の入力チェック
  if( empty($_POST['view_name']) ) {
    $error_message[] = '表示名を入力してください。';
  } else {
    $clean['view_name'] = htmlspecialchars( $_POST['view_name'], ENT_QUOTES, 'UTF-8');
  }
  
  // メッセージの入力チェック
  if( empty($_POST['message']) ) {
    $error_message[] = 'ひと言メッセージを入力してください。';
  } else {
    $clean['message'] = htmlspecialchars( $_POST['message'], ENT_QUOTES, 'UTF-8');
  }

  if( empty($error_message) ) {

    if( $file_handle = fopen( FILENAME, "a") ) {

      // 書き込み日時を取得
      $current_date = date("Y-m-d H:i:s");

      // 書き込むデータを作成
      $data = "'".$_POST['view_name']."','".$_POST['message']."','".$current_date."'\n";

      // 書き込み
      fwrite( $file_handle, $data);

      // ファイルを閉じる
      fclose( $file_handle);

      $success_message = 'メッセージを書き込みました。';
    }
  }
}

---- 省略 ----

$cleanにサニタイズした後の値が入るうようにしています。

サニタイズの処理自体は、バリデーションの部分で「空じゃなかった場合」に実行されるようif文else文を加える形で追記しました。
これで、もし未入力だった場合はエラーメッセージを作成し、入力があった場合のみサニタイズを行うというコードになります。
これを「表示名」「ひと言メッセージ」のいずれも行うように2箇所設定します。

さらに、入力されたデータの中に改行がある場合の対応をしていきます。
表示名については改行を削除し、メッセージはbr要素へ置き換えるコードをそれぞれ追記します。

index.php

<?php

---- 省略 ----

if( !empty($_POST['btn_submit']) ) {

// 表示名の入力チェック
  if( empty($_POST['view_name']) ) {
    $error_message[] = '表示名を入力してください。';
  } else {
    $clean['view_name'] = htmlspecialchars( $_POST['view_name'], ENT_QUOTES, 'UTF-8');
    $clean['view_name'] = preg_replace( '/\\r\\n|\\n|\\r/', '', $clean['view_name']);
  }

  // メッセージの入力チェック
  if( empty($_POST['message']) ) {
    $error_message[] = 'ひと言メッセージを入力してください。';
  } else {
    $clean['message'] = htmlspecialchars( $_POST['message'], ENT_QUOTES, 'UTF-8');
    $clean['message'] = preg_replace( '/\\r\\n|\\n|\\r/', '<br>', $clean['message']);
  }

  if( empty($error_message) ) {

    if( $file_handle = fopen( FILENAME, "a") ) {

      // 書き込み日時を取得
      $current_date = date("Y-m-d H:i:s");

      // 書き込むデータを作成
      $data = "'".$_POST['view_name']."','".$_POST['message']."','".$current_date."'\n";

      // 書き込み
      fwrite( $file_handle, $data);

      // ファイルを閉じる
      fclose( $file_handle);

      $success_message = 'メッセージを書き込みました。';
    }
  }
}

---- 省略 ----

改行コード「\r\n」「\n」「\r」をそれぞれ検索し、表示名の場合は空文字に置き換えて削除を行なっています。
もう1つのひと言メッセージでは「<br>」に置き換えました。

続いて、ファイルへの書き込みデータを作成する箇所でサニタイズした値を使用するように変更します。

index.php

<?php

---- 省略 ----

if( !empty($_POST['btn_submit']) ) {

  // 表示名の入力チェック
  if( empty($_POST['view_name']) ) {
    $error_message[] = '表示名を入力してください。';
  } else {
    $clean['view_name'] = htmlspecialchars( $_POST['view_name'], ENT_QUOTES, 'UTF-8');
    $clean['view_name'] = preg_replace( '/\\r\\n|\\n|\\r/', '', $clean['view_name']);
  }

  // メッセージの入力チェック
  if( empty($_POST['message']) ) {
    $error_message[] = 'ひと言メッセージを入力してください。';
  } else {
    $clean['message'] = htmlspecialchars( $_POST['message'], ENT_QUOTES, 'UTF-8');
    $clean['message'] = preg_replace( '/\\r\\n|\\n|\\r/', '<br>', $clean['message']);
  }

  if( empty($error_message) ) {

    if( $file_handle = fopen( FILENAME, "a") ) {
  
      // 書き込み日時を取得
      $current_date = date("Y-m-d H:i:s");

      // 書き込むデータを作成
      $data = "'".$clean['view_name']."','".$clean['message']."','".$current_date."'\n";

      // 書き込み
      fwrite( $file_handle, $data);

      // ファイルを閉じる
      fclose( $file_handle);

      $success_message = 'メッセージを書き込みました。';
    }
  }
}

---- 省略 ----

表示名の$_POST['view_name']だったところを$clean['view_name']に変更し、同じようにひと言メッセージの$_POST['message']$clean['message']へ置き換えます。
以上で、ファイルにはサニタイズされた値が書き込まれるようになりました。

サニタイズの効果を確認する

ここまで作成したサニタイズがどのような効果があるか確認していきましょう。
ページを再読み込みし、先ほどと同じJavaScriptのコードを入力して書き込んでみます。

もう一度同じJavaScriptコードを入力してみる

書き込みを行うと、次のように通常のテキストとして書き込まれました。

JavaScriptコードが通常の文字として書き込まれる

message.txt」を開いて、JavaScriptコードがどのようになっているか確認してみましょう。

message.txtに書き込まれたテキスト例

'JS太郎','&lt;script&gt;alert(&quot;Hello&quot;);&lt;/script&gt;','2019-03-25 22:12:51'

コードの記号がそのままではなく、ちゃんとHTMLエンティティに変換されて書き込まれていることが確認できました。
サニタイズの効果によってJavaScriptコードはただの文字列として登録されるようになっています。

続いて、もう1つの「' (シングルクォーテーション)」も入力してみます。

シングルクォーテーションをもう一度入力してみる

この文字を書き込むと、次のように掲示板に表示されます。

掲示板に書き込まれた例

こちらも「message.txt」にどのように書き込まれたか確認してみましょう。

message.txtに書き込まれたテキスト例

'シングル太郎','不正なコード&#039;,&#039;シングルクォーテーションを打つとmessage.txtのフォーマットが崩れてしまう&#039;,&#039;&#039;,&#039;&#039;&#039;,&#039;&#039;&#039;,&#039;2022-15-22 22:22:22&#039;
&#039;さらに追加で&#039;,&#039;メッセージを登録することもできる&#039;,&#039;2023-66-55 22:22:22&#039;&#039;,2019-03-25 20:05:42&#039;','2019-03-25 23:08:09'

HTMLエンティティが多くごちゃごちゃしていますが、「'」は「&#039;」に変換されていることがわかります。

以上、サニタイズがしっかり機能し、コードや記号が入力されても大丈夫なようになりました。

今回までは書き込まれたデータをテキストファイルに書き込んできましたが、次回からはデータベースに保存していくようにシステム改修をしていきます。

今回作成したコード:Github

前のページ投稿の未入力バリデーションをつける

次のページ投稿データの保存にデータベースを使う

記事一覧

  1. ひと言掲示板を作る
  2. メッセージの入力フォームを作る
  3. メッセージのデータを受け取る
  4. ファイルにデータを保存する
  5. ファイルからデータを読み込む
  6. 投稿完了メッセージを表示する
  7. 投稿の未入力バリデーションをつける
  8. 投稿されたデータをサニタイズする
  9. 投稿データの保存にデータベースを使う
  10. 掲示板のデータベースとテーブルを作成する
  11. 投稿データをデータベースに登録する
  12. データベースからデータを取得する
  13. コードを整理する
  14. セッションで表示名の入力を省略する
  15. 管理ページを作成する
  16. ログインページを作る
  17. 投稿データをCSV形式でダウンロードする
  18. 投稿データをダウンロードする件数を指定する
  19. 投稿メッセージの編集ページを作る
  20. メッセージを編集する機能を実装する
  21. 投稿メッセージの削除ページを作る
  22. 管理ページにログアウトを実装する
  23. リロードによる多重投稿を防止する
  24. 投稿に文字数制限を設定する

前の記事

記事一覧

次の記事