「HTML エスケープ」で何を守りたいのか
まず、ここをちゃんとイメージしてほしいです。
ユーザー入力をそのまま画面に出すと、こういうことが起きます。
$name = $_GET['name'] ?? 'ゲスト';
echo "こんにちは、{$name} さん!";
PHPここに、もしユーザーがこんな値を入れたらどうなるか。
<script>alert('XSS');</script>
ブラウザはそれを「ただの文字」ではなく「JavaScript」として実行してしまいます。
つまり、
「ユーザーが入力した文字列が、そのまま HTML として解釈されてしまう」
これが一番危ないポイントです。
HTML エスケープは、これを防ぐための「必須の防具」です。
HTML エスケープの正体:「記号を安全な文字列に変える」
危ないのはどんな文字か
HTML の中で特別な意味を持つ記号があります。
代表的なのはこのあたりです。
<>&"'(場合による)
これらがそのまま出てくると、
ブラウザは「タグ」「属性」「エンティティ」として解釈しようとします。
それを「ただの文字」に変える
HTML エスケープは、これらを「エンティティ」と呼ばれる形に変えます。
< → <
> → >
& → &
" → "
' → '(または ')
例えば、さっきの危険な入力をエスケープするとこうなります。
<script>alert('XSS');</script>
↓
<script>alert('XSS');</script>
ブラウザはこれを「タグ」ではなく「ただの文字列」として表示します。
これが 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 はこうです。
こんにちは、<script>alert('XSS')</script> さん!
画面には、
こんにちは、<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; のように、
エンティティがさらにエスケープされてしまい、
画面に「<」と表示されてしまいます。
なので、設計としてはこう決めてください。
「DB には生の値(そのまま)を保存する」
「画面に出す直前にだけエスケープする」
これを徹底すると、二重エスケープ問題から解放されます。
テンプレート側のルールを決める
例えば、ビュー(テンプレート)側ではこう決めます。
<?= h($value) ?>で出す<?= $value ?>は原則禁止(本当に安全なときだけ例外)
<p>名前:<?= h($user['name']) ?></p>
<p>メール:<?= h($user['email']) ?></p>
PHP「画面に出すときは必ず h() を通す」という癖をつけると、
XSS のリスクが一気に下がります。
場所によってエスケープの仕方が変わる、という話も少しだけ
初心者向けには「とりあえず htmlspecialchars」で十分ですが、
実務では「どこに出すか」で必要なエスケープが変わる、という話もあります。
ざっくりだけ触れておきます。
HTML 本文の中
<p><?= h($value) ?></p>
ここは、htmlspecialchars(h())で 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>
PHPnl2br(h(...)) のように、
「まずエスケープしてから改行を <br> に変換する」
という順番にするのもポイントです。
まとめ:今日からの「HTML エスケープ」ユーティリティ
大事なところだけ、ぎゅっとまとめます。
HTML エスケープは、
- ユーザー入力などの文字列を、そのまま HTML として解釈されないようにするための「防具」。
- PHP では
htmlspecialcharsを使うのが基本。 - 実務では
htmlspecialchars($value, ENT_QUOTES, 'UTF-8')をh()というラッパー関数にして使うとよい。 - DB には生の値を保存し、「画面に出す直前」にだけエスケープするのが鉄則。
- テンプレートでは「
<?= h(...) ?>がデフォルト、<?= ... ?>は例外」というルールにすると安全。

