HTMLPurifier による HTML サニタイズ
a-blog cms は、エントリー本文などユーザーが入力した HTML をテンプレート変数として出力する際、HTMLPurifier を使って XSS につながる要素・属性を取り除きます。このページでは、標準のサニタイズ処理と、設定・Hook による拡張方法を説明します。
サニタイズが適用される場面
サニタイズは変数内の HTML を保存するときではなく、出力するときに適用されます。代表的には次の場面です。
エントリー本文をテンプレート変数(
{var}など)として出力するときページキャッシュを再生成するとき
システム更新時にコンテンツを再生成するとき
出力のたびに適用されるため、dangerous_tags や iframe の許可ホストといった設定を変更すると、既存のエントリーもキャッシュの再生成タイミングで新しい方針に沿って出力し直されます(データベースに保存されている HTML 自体が書き換わるわけではありません)。
自作のモジュールや拡張から同じサニタイズ処理を利用したい場合は、次のように呼び出せます。
$html = Acms\Sevrvices\Facades\Application::make('html-purifier')->clean($html);サニタイズ設定
禁止タグ(dangerous_tags)
private/config.system.default.yaml のデフォルト値は次のとおりです。
dangerous_tags: ['script', 'form']ここに列挙したタグは丸ごと削除されます。<font> など HTML 標準で非推奨・廃止されたタグは、dangerous_tags の指定に関わらず常に削除されます。
iframe は dangerous_tags ではなく、次に説明する whitelist で制御します。
iframe のホスト whitelist (Ver. 3.2.25 ~)
html_purifier_iframe_allowed_hosts で、src を許可するホスト(パス prefix 可)を指定します。
html_purifier_iframe_allowed_hosts: [
'www.youtube.com/embed|fullscreen; encrypted-media; picture-in-picture|',
'www.youtube-nocookie.com/embed|fullscreen; encrypted-media; picture-in-picture|',
'player.vimeo.com/video|fullscreen; picture-in-picture|',
'www.google.com/maps/embed'
]書式は host[/pathPrefix][|allow[|sandbox]] です。
要素 | 説明 |
|---|---|
| 許可する |
| セミコロン区切りの Permissions-Policy 機能トークン(例: |
| スペース区切りの sandbox トークン |
allow / sandbox は「編集者が要求できる capability の許可リスト」です。エントリー側の <iframe> に書かれた allow(セミコロン区切り)/ sandbox(スペース区切り)のうち、ここに列挙したトークンだけが残り、許可リスト外のトークンは削除されます。編集者が allow / sandbox を書いていなければ何も付与されません(強制で付与されることはありません)。ホスト部分だけを書いた場合は許可リストが空になり、編集者が何を書いても capability が付与されない最小権限になります。
許可ホストにマッチしない
<iframe>は、srcだけが取り除かれて空の<iframe>として残ります(要素ごと削除されるわけではありません)。html_purifier_iframe_allowed_hosts: []にすると、すべての iframe のsrcが無効化されます。設定は
private/config.system.yaml側で上書き(追加・削除)できます。
HtmlPurifier によるサニタイズは HTML の構造・属性の許可リストを前提に構成されています。iframe whitelist は src を縛るものであり、フレーム表示の最終的な可否はHTTPヘッダー側の X-Frame-Options / CSP frame-ancestors にも依存します。多層防御の観点では、CSP の frame-src も別途あわせて運用してください。
Hook による拡張
dangerous_tags や whitelist だけで表現できない要件(独自タグ・属性の許可、URI スキームの追加など)は、extendsHtmlPurifierConfig / extendsHtmlPurifierDefinition の 2 つのフックで拡張します。サイト拡張(extension/acms/Hook.php)やプラグインのフッククラスに実装します。
2 つのフックの違い(Ver. 3.2.25 ~)
フック | 発火タイミング | 用途 |
|---|---|---|
| カスタム HTML 定義を取得する前 |
|
| カスタム HTML 定義を取得した後 |
|
HTML.* 系の設定は定義の取得前に確定させる必要があるため、許可タグ・属性・URI スキームの調整は必ず extendsHtmlPurifierConfig 側で行ってください。定義取得後にしか行えない要素・属性の追加(addElement / addAttribute)は extendsHtmlPurifierDefinition 側で行います。
/**
* HTMLPurifier の設定構築時に呼ばれる拡張ポイント(カスタム HTML 定義の取得前)
*/
public function extendsHtmlPurifierConfig(\HTMLPurifier_Config $config): void
{
// 例: data: スキームの画像を許可する
$config->set('URI.AllowedSchemes', ['http' => true, 'https' => true, 'mailto' => true, 'data' => true]);
// 例: 特定タグをさらに禁止する(既定値を上書き)
$config->set('HTML.ForbiddenElements', ['script', 'iframe', 'form', 'object']);
}
/**
* HTMLPurifier のカスタム HTML 定義の構築時に呼ばれる拡張ポイント(定義の取得後)
*/
public function extendsHtmlPurifierDefinition(\HTMLPurifier_HTMLDefinition $def, \HTMLPurifier_Config $config): void
{
// 例: <details> / <summary> を許可し、独自 data 属性を通す
$def->addElement('details', 'Block', 'Flow', 'Common', ['open' => 'Bool#open']);
$def->addElement('summary', 'Inline', 'Inline', 'Common');
$def->addAttribute('div', 'data-my-widget', 'CDATA');
}
雛形(ablogcms/extension/acms/Hook.php)にも同じ形の空実装とサンプルコメントが用意されています。
キャッシュ
カスタム HTML 定義はキャッシュされます。iframe whitelist(html_purifier_iframe_allowed_hosts)を config で変更した場合は自動でキャッシュが再生成されますが、extendsHtmlPurifierConfig / extendsHtmlPurifierDefinition フックで追加・変更した内容は自動では反映されません。
フックの実装を追加・変更したときは、必ず管理画面からキャッシュを削除してください。 削除すると html-purifier 用のキャッシュディレクトリが消え、次回アクセス時に新しい定義でキャッシュが再生成されます。挙動が反映されていないように見えたときも、まずキャッシュ削除を試してください。
注意点
extendsHtmlPurifierConfig/extendsHtmlPurifierDefinitionはフック機能が有効(HOOK_ENABLE)であることが前提です。フックを無効化している環境では呼ばれません。独自属性を許可する際は、フリーフォームの
CDATAを安易に増やさず、可能な限りEnumやBoolなど型付きのAttrDefを使ってください。