PHP Tips | 文字列処理:フォーマット – HTML エスケープ

PHP PHP
スポンサーリンク

「HTML エスケープ」で何を守りたいのか

まず、ここをちゃんとイメージしてほしいです。

ユーザー入力をそのまま画面に出すと、こういうことが起きます。

$name = $_GET['name'] ?? 'ゲスト';

echo "こんにちは、{$name} さん!";
PHP

ここに、もしユーザーがこんな値を入れたらどうなるか。

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

ブラウザはそれを「ただの文字」ではなく「JavaScript」として実行してしまいます。

つまり、

「ユーザーが入力した文字列が、そのまま HTML として解釈されてしまう」

これが一番危ないポイントです。

HTML エスケープは、これを防ぐための「必須の防具」です。


HTML エスケープの正体:「記号を安全な文字列に変える」

危ないのはどんな文字か

HTML の中で特別な意味を持つ記号があります。

代表的なのはこのあたりです。

  • <
  • >
  • &
  • "
  • '(場合による)

これらがそのまま出てくると、
ブラウザは「タグ」「属性」「エンティティ」として解釈しようとします。

それを「ただの文字」に変える

HTML エスケープは、これらを「エンティティ」と呼ばれる形に変えます。

<  →  &lt;
>  →  &gt;
&  →  &amp;
"  →  &quot;
'  →  &#039;(または &apos;)

例えば、さっきの危険な入力をエスケープするとこうなります。

<script>alert('XSS');</script>
↓
&lt;script&gt;alert(&#039;XSS&#039;);&lt;/script&gt;

ブラウザはこれを「タグ」ではなく「ただの文字列」として表示します。

これが HTML エスケープの本質です。


PHP の基本関数:htmlspecialchars を使いこなす

一番使うのは htmlspecialchars

PHP で HTML エスケープするときの主役は、htmlspecialchars です。

string htmlspecialchars(
    string $string,
    int $flags = ENT_COMPAT | ENT_HTML401,
    ?string $encoding = null,
    bool $double_encode = true
)
PHP

初心者向けに、まずはこう覚えてください。

「画面に出す前の文字列は、とりあえず htmlspecialchars に通す」

最低限これだけは書いてほしい形

実務でよく使う、安全寄りの書き方はこれです。

function h(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}
PHP

ここでのポイントは3つあります。

ENT_QUOTES
シングルクォート ' もダブルクォート " も両方エスケープします。
属性値の中に入れるときに安全です。

‘UTF-8’
文字コードを明示します。
アプリ全体が UTF-8 前提なら、ここも必ず UTF-8 にしておきます。

短いラッパー関数 h()
毎回 htmlspecialchars(..., ENT_QUOTES, 'UTF-8') と書くのは面倒なので、
h() という短い関数名で包んでおくと、テンプレートが読みやすくなります。


具体例:エスケープあり/なしの違いを体感する

例1:名前を表示するだけのつもりが…

$name = $_GET['name'] ?? 'ゲスト';

echo "こんにちは、{$name} さん!";
PHP

ここに、次のような URL でアクセスされたとします。

/?name=<script>alert('XSS')</script>

ブラウザが解釈する HTML はこうなります。

こんにちは、<script>alert('XSS')</script> さん!

結果として、アラートが実行されます。

ちゃんとエスケープするとこうなる

function h(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}

$name = $_GET['name'] ?? 'ゲスト';

echo "こんにちは、". h($name) ." さん!";
PHP

同じ URL でアクセスしても、ブラウザが解釈する HTML はこうです。

こんにちは、&lt;script&gt;alert(&#039;XSS&#039;)&lt;/script&gt; さん!

画面には、

こんにちは、<script>alert('XSS')</script> さん!

と「文字として」表示されるだけで、スクリプトは実行されません。

これが HTML エスケープの威力です。


どこでエスケープするべきか(ここが超重要)

原則:「出力の直前でエスケープ」

よくある失敗は、

  • 入力時にエスケープして DB に保存してしまう
  • その後、さらに出力時にもエスケープして「二重エスケープ」になる

というパターンです。

例えば、こうなります。

// 入力時にエスケープして保存(NG)
$safe = htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
// DB に $safe を保存

// 表示時にもエスケープ(NG)
echo htmlspecialchars($safe, ENT_QUOTES, 'UTF-8');
PHP

<&lt;&amp;lt; のように、
エンティティがさらにエスケープされてしまい、
画面に「<」と表示されてしまいます。

なので、設計としてはこう決めてください。

「DB には生の値(そのまま)を保存する」
「画面に出す直前にだけエスケープする」

これを徹底すると、二重エスケープ問題から解放されます。

テンプレート側のルールを決める

例えば、ビュー(テンプレート)側ではこう決めます。

  • <?= h($value) ?> で出す
  • <?= $value ?> は原則禁止(本当に安全なときだけ例外)
<p>名前:<?= h($user['name']) ?></p>
<p>メール:<?= h($user['email']) ?></p>
PHP

「画面に出すときは必ず h() を通す」という癖をつけると、
XSS のリスクが一気に下がります。


場所によってエスケープの仕方が変わる、という話も少しだけ

初心者向けには「とりあえず htmlspecialchars」で十分ですが、
実務では「どこに出すか」で必要なエスケープが変わる、という話もあります。

ざっくりだけ触れておきます。

HTML 本文の中

<p><?= h($value) ?></p>

ここは、htmlspecialcharsh())で OK です。

HTML 属性の中

<input type="text" value="<?= h($value) ?>">

ここも、ENT_QUOTES を付けた htmlspecialchars なら OK です。

だからこそ、h() の中で ENT_QUOTES を指定しておくのが大事です。

JavaScript の中、URL の中など

<script>
  const msg = "<?= ??? ?>";
</script>

ここは、本当は別のエスケープが必要になります(JavaScript 用のエスケープなど)。

ただ、初心者のうちは、

  • 「JavaScript の中に直接ユーザー入力を埋め込まない」
  • 「どうしても必要なら、まずはサーバー側で JSON にしてから埋め込む」

くらいのルールにしておくと安全です。


実務ユーティリティとしてのまとめ方

プロジェクトのどこからでも使える h() を用意する

一番シンプルで効果的なのは、
プロジェクト共通のファイルに h() を定義しておくことです。

// common.php など
function h(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}
PHP

テンプレートやコントローラからは、ひたすらこれを使う。

// view.php
require 'common.php';
?>
<p>名前:<?= h($user['name']) ?></p>
<p>コメント:<?= nl2br(h($comment['body'])) ?></p>
PHP

nl2br(h(...)) のように、
「まずエスケープしてから改行を <br> に変換する」
という順番にするのもポイントです。


まとめ:今日からの「HTML エスケープ」ユーティリティ

大事なところだけ、ぎゅっとまとめます。

HTML エスケープは、

  • ユーザー入力などの文字列を、そのまま HTML として解釈されないようにするための「防具」。
  • PHP では htmlspecialchars を使うのが基本。
  • 実務では htmlspecialchars($value, ENT_QUOTES, 'UTF-8')h() というラッパー関数にして使うとよい。
  • DB には生の値を保存し、「画面に出す直前」にだけエスケープするのが鉄則。
  • テンプレートでは「<?= h(...) ?> がデフォルト、<?= ... ?> は例外」というルールにすると安全。

タイトルとURLをコピーしました