このユーティリティがやりたいことの全体像
「指定タグのみ許可して HTML サニタイズ」というのは、ざっくり言うとこういうことです。
ユーザー入力などの HTML をそのまま表示すると危ないので、
危険なタグや余計なタグは全部消して、
「これだけは OK」と決めたタグだけを残したい。
例えば、
<p>こんにちは、<strong>世界</strong>!</p>
<script>alert('XSS');</script>
という入力が来たときに、
<p>こんにちは、<strong>世界</strong>!</p>
のように、「<p> と <strong> だけ残して、それ以外は全部落とす」イメージです。
ここで大事なのは、
- 「全部のタグを消す」のではなく
- 「一部のタグだけを許可する」
という点です。
これが「指定タグのみ許可して HTML サニタイズ」という考え方です。
まず整理:エスケープとサニタイズの違い
HTML エスケープとは
htmlspecialchars などで、
<script>alert('XSS');</script>
PHPを
<script>alert('XSS');</script>
のように、「タグとして動かないただの文字」に変えるのが「エスケープ」です。
これは、
- HTML として解釈させたくない
- 画面に「そのままの文字」として見せたい
ときに使います。
HTML サニタイズとは
一方、「サニタイズ」は、
HTML として表示したいけれど、危険なものは取り除きたい
というときに使う考え方です。
- 太字や改行などの装飾は残したい
- でも
<script>やonclickなどは消したい
というような場面ですね。
今回のテーマはこの「サニタイズ」、しかも
「指定したタグだけ許可して、それ以外は全部落とす」
というスタイルのサニタイズです。
PHP 標準でできること:strip_tags の「許可タグ」機能
strip_tags で「タグを消す+一部だけ残す」
前の「HTML タグ除去」で出てきた strip_tags には、
「このタグだけは残していいよ」と指定できる機能があります。
基本形はこうです。
$html = '<p>こんにちは、<strong>世界</strong>!<script>alert("XSS");</script></p>';
$sanitized = strip_tags($html, '<p><strong>');
echo $sanitized;
// <p>こんにちは、<strong>世界</strong>!</p>
PHPここでやっていることは、
<p>と<strong>は残す- それ以外のタグ(ここでは
<script>)は全部削除
という動きです。
第2引数に「許可したいタグ」を文字列で渡します。
'<p><strong><em><br>'
PHPのように、タグをそのまま並べて書きます。
閉じタグ </p> などは書かなくて OK です。
ここまででできること・できないこと
strip_tags の「許可タグ」機能でできるのは、
- 許可したタグ以外を全部消す
- 許可したタグは、そのままの形で残す
というところまでです。
逆に言うと、
- 許可したタグについて「属性(onclick など)までチェックする」ことはできない
<a href="javascript:...">のような「危険な属性」を消すことはできない
という限界があります。
ここが「重要ポイント」なので、もう少し深掘りします。
重要ポイント:タグだけでなく「属性」も危険になりうる
危険なのは <script> だけじゃない
初心者のうちは、
「危ないのは
<script>でしょ?」
と思いがちですが、実際にはタグの「属性」も危険になりえます。
例えば、こんな HTML を考えてみましょう。
<a href="javascript:alert('XSS')">クリック</a>
タグ自体は <a> で、一見「リンクだから安全そう」に見えますが、href に javascript: が入っているので、クリックすると JavaScript が実行されます。
strip_tags で <a> を許可すると、こうなります。
$html = '<a href="javascript:alert(1)">クリック</a>';
echo strip_tags($html, '<a>');
// <a href="javascript:alert(1)">クリック</a>
PHPタグは残しつつ「危険な属性だけ消す」ということは、strip_tags だけではできません。
実務でどう考えるか
ここで現実的な選択肢は大きく2つです。
1. 属性を一切許可しない(タグだけ残す)
2. 専用のサニタイズライブラリを使う
初心者向け・シンプルなユーティリティとしては、
まずは「1. 属性を全部落とす」パターンを押さえておくのが現実的です。
シンプルな実装例:指定タグだけ残し、属性は全部削除する
ステップの考え方
やりたいことを分解すると、こうなります。
- 許可していないタグは全部削除する
- 許可したタグについても、属性は全部削除して「素のタグ」だけにする
例えば、入力がこうだったとします。
<p class="lead">こんにちは、<strong style="color:red;">世界</strong>!</p>
<script>alert("XSS");</script>
これを、
<p>こんにちは、<strong>世界</strong>!</p>
のようにしたい、というイメージです。
実装例:strip_tags + preg_replace で属性を削除
正確な HTML パースは本当は難しいのですが、
「シンプルなケースを対象にした実務ユーティリティ」と割り切るなら、次のような書き方がよく使われます。
function sanitizeHtmlAllowTags(array $allowedTags, string $html): string
{
// 1. 許可タグを文字列に変換(例: ['p', 'strong'] -> '<p><strong>')
$allowedTagString = '';
foreach ($allowedTags as $tag) {
$allowedTagString .= '<' . $tag . '>';
}
// 2. 許可タグ以外のタグを strip_tags で削除
$html = strip_tags($html, $allowedTagString);
// 3. 許可タグについても、属性は全部削除する
// <tag ...> を <tag> に変換するイメージ
$html = preg_replace('/<([a-z0-9]+)[^>]*>/i', '<$1>', $html);
return $html;
}
PHP使い方はこんな感じです。
$html = '<p class="lead">こんにちは、<strong style="color:red;">世界</strong>!</p>'
. '<script>alert("XSS");</script>';
$sanitized = sanitizeHtmlAllowTags(['p', 'strong'], $html);
echo $sanitized;
// <p>こんにちは、<strong>世界</strong>!</p>
PHPこの実装のポイント解説
1. 許可タグの指定を配列で受ける
['p', 'strong', 'br']
PHPのように配列で受けておくと、呼び出し側が分かりやすくなります。
2. strip_tags で「許可タグ以外」を全部削る
$html = strip_tags($html, '<p><strong>');
PHPの部分で、<script> や <div> など、許可していないタグは全部消えます。
3. preg_replace で「属性」を全部削る
$html = preg_replace('/<([a-z0-9]+)[^>]*>/i', '<$1>', $html);
PHPこの正規表現は、
<に続くタグ名(([a-z0-9]+))をキャプチャして- その後ろに続く
[^>]*(>までの属性部分)を全部無視し <タグ名>だけに置き換える
という動きをします。
つまり、
<p class="lead">
は <p> に、
<strong style="color:red;">
は <strong> に変換されます。
これで、
- 許可していないタグは strip_tags で消える
- 許可したタグも「素のタグ」だけ残る(属性は全部削除)
という状態を作れます。
実務での具体的なシチュエーション例
例題1:お知らせ本文で「最低限の装飾だけ許可」したい
管理画面から「お知らせ本文」を入力できるようにしていて、
担当者には「太字と改行だけ使っていいですよ」と伝えているケースを考えます。
$inputHtml = $_POST['body'] ?? '';
// 許可するタグは <p>, <br>, <strong> のみ
$sanitized = sanitizeHtmlAllowTags(['p', 'br', 'strong'], $inputHtml);
// あとは $sanitized をそのまま HTML として表示
echo $sanitized;
PHPこうしておけば、
<script>や<iframe>などは全部消える<p>,<br>,<strong>以外のタグも消える- 許可したタグについても、
styleやonclickなどの属性は全部削除される
という状態になります。
「見た目の装飾は最低限でいいから、とにかく安全寄りにしたい」というときの、現実的な落としどころです。
例題2:ユーザーのプロフィール自己紹介欄
ユーザーが自分のプロフィールに「自己紹介文」を書ける機能を考えます。
- 完全なプレーンテキストだと味気ない
- でも、あまり自由に HTML を書かせるのも怖い
というときに、
「改行(
<br>)とリンク(<a>)だけ許可」
というルールにする、というのはよくあるパターンです。
$inputHtml = $_POST['profile'] ?? '';
// <a> と <br> だけ許可
$sanitized = sanitizeHtmlAllowTags(['a', 'br'], $inputHtml);
echo $sanitized;
PHPただし、この場合も href 属性は削除されるので、
本当に「リンクとして機能させたい」なら、もう一段工夫が必要になります(後述)。
もう一歩:リンクだけは href を許可したい場合の考え方
ここから先は少しレベルが上がりますが、実務でよく出る悩みなので触れておきます。
「基本的には属性を全部消したい。
でも<a>のhrefだけは許可したい。」
というケースです。
ここまで来ると、本格的には HTML パーサーや専用ライブラリを使う世界になりますが、
考え方だけ整理しておきます。
考え方のステップ
- まず
strip_tagsで許可タグ以外を削る DOMDocumentなどで HTML をパースする- タグごとに「許可する属性」を決めて、それ以外は削除する
hrefについても、「javascript:を禁止する」などのチェックを入れる
これは初心者向けの「軽いユーティリティ」の範囲を超えるので、
ここでは「属性を全部削るシンプル版」をメインとして押さえておくのが現実的です。
重要ポイントのまとめと実務でのスタンス
重要ポイント
- 「指定タグのみ許可して HTML サニタイズ」は、
「全部消す」のではなく「一部だけ残す」発想。 - PHP 標準の
strip_tagsで「許可タグ以外を削る」ことは簡単にできる。 - しかし
strip_tagsは「属性の安全性」までは見てくれない。 - シンプルな実務ユーティリティとしては、
「許可タグ以外を削る」+「許可タグの属性も全部削る」
という方針が安全寄りで分かりやすい。
実務でのスタンス
- 「最低限の装飾だけ許可したい」「とにかく安全寄りでいい」
→ 今日紹介したような「タグだけ残して属性は全部削る」ユーティリティがちょうどいい。 - 「リンクの href もちゃんと使いたい」「もっと細かく制御したい」
→ 専用の HTML サニタイズライブラリや HTML パーサーを検討する段階。
今日から使えるテンプレ関数
最後に、今回の話をギュッと凝縮した「実務でそのまま使える形」をもう一度まとめます。
/**
* 指定したタグだけを許可し、その他のタグは削除。
* 許可したタグについても、属性はすべて削除する。
*
* 例:
* sanitizeHtmlAllowTags(['p', 'strong'], $html);
*/
function sanitizeHtmlAllowTags(array $allowedTags, string $html): string
{
// 許可タグを strip_tags 用の文字列に変換
$allowedTagString = '';
foreach ($allowedTags as $tag) {
$allowedTagString .= '<' . $tag . '>';
}
// 許可タグ以外のタグを削除
$html = strip_tags($html, $allowedTagString);
// 許可タグについても属性を削除(<tag ...> -> <tag>)
$html = preg_replace('/<([a-z0-9]+)[^>]*>/i', '<$1>', $html);
return $html;
}
PHPまずはこれを「自分のプロジェクトのどこか一箇所」に組み込んでみると、
HTML 入力まわりの安心感が一段上がります。
