エスケープ処理

特殊文字の危険性

HTMLでは ” ‘ & < > などの特殊文字で記述されています。しかしこの特殊文字を悪意を持ったユーザが直接フォーム送信したとき、プログラムを誤動作させたり、データ改竄など予期せぬ問題を発生させることができます。

特殊文字とエスケープ文字

悪意のある特殊文字のデータは、文字をエスケープして対策できます。HTMLの特殊文字とエスケープ文字は以下のようになります。

特殊文字のエスケープ例
特殊文字 エスケープ文字
< &lt;
> &gt;
& &amp;
" &quot;
' &apos;

エスケープ対策

htmlspecialchars()

PHPでは文字列をエスケープする関数の1つに htmlspecialchars() があり、以下のようにエスケープするのがよいでしょう。

htmlspecialchars(文字列, ENT_QUOTES, 'UTF-8');
  • 第1引数:エスケープしたい文字列
  • 第2引数: エスケープのモードで、ENT_QUOTES が一般的
  • 第3引数: 文字コードで、UTF-8 が一般的 (任意)

特殊文字をエスケープ

htmlspecialchars() でHTML特殊文字をエスケープしてみましょう。

<?php
echo htmlspecialchars('<', ENT_QUOTES, 'UTF-8');
echo PHP_EOL;

echo htmlspecialchars('>', ENT_QUOTES, 'UTF-8');
echo PHP_EOL;

echo htmlspecialchars("'", ENT_QUOTES, 'UTF-8');
echo PHP_EOL;

echo htmlspecialchars('"', ENT_QUOTES, 'UTF-8');
echo PHP_EOL;

echo htmlspecialchars('&', ENT_QUOTES, 'UTF-8');
echo PHP_EOL;
結果
&lt;
&gt;
&#039;
&quot;
&amp;

このようにプログラムでデータ処理する前に、事前に文字列をエスケープしていた方がより安全にプログラムを実行することができます。

サニタイズとは

フォーム送信されたデータを書き込み前にチェック・制限することをサニタイズといいます。悪意のあるデータを防ぐための処理です。

htmlspecialchars() で特殊文字変換

PHP ではhtmlspecialchars() を使って特殊文字を変換します。

変換後の文字列 = htmlspecialchars(文字列);

XSS攻撃

Webアプリでのデータ送信は、悪意のあるJavaScript のコードを実行させるXSS(クロスサイトスクリプティング) の対策が必要です。例えば、入力画面で JavaScript のプログラムを入力してみます。

<script>alert('XSS攻撃');</script>

この入力で、確認画面に進むと JavaScript が実行されます。

htmlspecialchars()

htmlspecialchars() は、HTMLの特殊文字をエスケープする関数で、指定のオプションで変換します。

データ = htmlspecialchars(データ, ENT_QUOTES, 'UTF-8');

サニタイズ

入力データが「JavaScript」や「SQL」など誤動作しないように文字変換することをサニタイズといいます。

サニタイズメソッドの作成

各データをhtmlspecialchars() で文字変換する、check() メソッドを定義してみます。

function check($posts)
{
    if (empty($posts)) return;
    foreach ($posts as $column => $post) {
        $posts[$column] = htmlspecialchars($post, ENT_QUOTES, 'UTF-8');
    }
    return $posts;
}

サニタイズの実行

メイン処理で、POSTデータをサニタイズします。

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
     //サニタイズ
    $user = check($_POST);
    $errors = validate($user);

    $_SESSION['user'] = $user;
    $_SESSION['errors'] = $errors;

    if ($errors) {
        header('Location: input.php');
    }
}

確認画面でサニタイズ

確認画面 confirm.php でサニタイズしてみましょう。check() メソッドを追加し、データを foreach で1つずつチェックします。

function check($posts)
{
    if (empty($posts)) return;
    foreach ($posts as $column => $post) {
        $posts[$column] = htmlspecialchars($post, ENT_QUOTES, 'UTF-8');
    }
    return $posts;
}

$_POST をサニタイズして、$posts に代入します。

//サニタイズ
$posts = check($_POST);
$errors = validate($posts);

XSS対策の確認

不正なデータを入力して、JavaScriptが実行されないか確認してみましょう。

ブラウザ上では、JavaScriptのコードが表示され、HTMLソースでは特殊文字がエスケープされています。

ブラウザ上の表示
<script>alert('XSS攻撃');</script>
HTMLソースの中身
&lt;script&gt;alert(&#039;ok&#039;)&lt;/script&gt;

ソース

confirm.php
<?php
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    exit;
}

$posts = check($_POST);
$errors = validate($posts);
if (!empty($errors)) {
    header('Location: input.php');
    exit;
}

function check($posts)
{
    if (empty($posts)) return;
    foreach ($posts as $column => $post) {
        $posts[$column] = htmlspecialchars($post, ENT_QUOTES, 'UTF-8');
    }
    return $posts;
}

function validate($posts)
{
    $errors = [];
    if (empty($posts['email'])) {
        $errors['email'] = 'メールアドレスを入力してください';
    }
    if (empty($posts['password'])) {
        $errors['password'] = 'パスワードを入力してください';
    }
    return $errors;
}
?>
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>

<body>
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-6">
                <h2 class="h2 text-center p-3">会員登録</h2>
                <form action="result.php" method="post">
                    <label class="form-label fw-bold" for="">氏名</label>
                    <p><?= $posts['name'] ?></p>

                    <label class="form-label fw-bold" for="">メールアドレス</label>
                    <p><?= $posts['email'] ?></p>

                    <div class="text-center p-3">
                        <p>この内容で登録しますか?</p>
                        <button class="btn btn-primary">登録</button>
                        <a href="input.php" class="btn btn-outline-primary">戻る</a>
                    </div>
                </form>
            </div>
        </div>
    </div>
</body>

</html>

PHP超入門