コンテンツへスキップ

esc_html() / esc_attr() など WordPress のエスケープ関数の基礎 — 「出力時に逃がす」というXSS対策の考え方

WordPressのテーマやプラグインのコードを読んでいると、echo esc_html( $title ); や <a href="<?php echo esc_url( $url ); ?>"> のように、値を出力する箇所を必ず関数で包んでいるのに気づく。これは「エスケープ関数」と呼ばれるコアの仕組みで、XSS(クロスサイトスクリプティング)と呼ばれる問題を防ぐための基本の道具である。今回は、なぜ関数が複数あるのか、そして「いつ」逃がすのかという考え方を整理する。

補足: XSS とは、Webページに表示される値の中にHTMLやスクリプトとして解釈される文字列が紛れ込み、ブラウザがそれをページの一部として実行・描画してしまう問題の総称。原因は「値」と「HTMLの構造」の境界が曖昧になることにある。前回の $wpdb->prepare()(値とSQL文の構造を分ける)と、根っこの発想は同じである。

なぜ「エスケープ」が必要なのか

たとえば、検索結果ページで「検索キーワード: ○○」と表示する場面を考える。キーワードはURLから渡ってくる外部由来の値なので、次のようにそのまま出力してしまうと問題になりうる。

// 良くない例(説明用の疑似コード): 外部由来の値をそのまま出力している
echo '<p>検索キーワード: ' . $_GET['s'] . '</p>';

$_GET['s'] に < や > を含む文字列が入ると、ブラウザはそれを文字としてではなく HTML のタグとして解釈してしまう。値のつもりで出力したものが、ページの構造の一部として扱われるわけである。

エスケープとは、こうした「HTMLの中で特別な意味を持つ文字」を、別の表記(HTMLエンティティ)に置き換えて、あくまで表示上の文字として扱われるようにする処理を指す。たとえば < は &lt;、> は &gt; に置き換わる。ブラウザ上では元の < として見えるが、タグとしては解釈されない。

出力先ごとに関数が分かれている理由

WordPressには用途別のエスケープ関数がいくつかある。分かれている理由は、「特別な意味を持つ文字」が出力先ごとに異なるからである。

関数 主な出力先 何を守っているか
esc_html() HTMLタグの間に置くテキスト < > & 等をエンティティに変換
esc_attr() HTML属性の値(class="..." 等) 引用符を含めて属性の外に出られないようにする
esc_url() href / src などのURL 不適切なスキームの除去・不正な文字の整形
esc_js() インラインのJavaScript内の文字列 文字列リテラルを壊す文字の処理

同じ文字列でも、タグの間に置くのか、属性の値に置くのか、URLとして置くのかによって、ブラウザが「構造」として解釈する境界が違う。属性値の中では引用符が境界になるし、URLではスキーム(https: など)が意味を持つ。だから、出力先に合った関数を選ぶことが要点になる。esc_html() で包んだ値を属性に入れたり、esc_attr() で包んだ値をURLとして使ったりすると、守れる範囲がずれてしまう。

「入力時」ではなく「出力時」に逃がす

もうひとつの重要な考え方は、エスケープは値を保存するときではなく、出力する直前に行うというものである。WordPressの開発ガイドラインでも、この方針が「Escape late(遅く逃がす)」と表現されている。

理由は、同じ値が複数の場所に出力されうるからである。データベースに保存された文字列は、あるときはHTMLのテキストとして、あるときは属性値として、あるときはURLの一部として使われるかもしれない。保存時に「HTML向け」に加工してしまうと、別の出力先では二重に変換されたり、逆に足りなかったりする。値は元の形のまま保持し、出力の瞬間に出力先に合った変換を行うほうが、境界を見失いにくい。

なお、保存時に行う sanitize_text_field() のような「入力の整形」と、出力時のエスケープは目的が別物で、どちらか一方で代用できるものではない。整形は「受け取る値として妥当な形にする」処理、エスケープは「出力先で誤解釈されないようにする」処理という役割分担になっている。

HTMLの一部を許可したいとき

ときには、値の中のHTMLを「すべて文字扱い」にはしたくない場面もある。たとえばアーカイブページの説明文のように、管理画面で入力した段落や強調タグを表示したい場合である。このためにWordPressには wp_kses_post() があり、許可リストに含まれるタグだけを残して、それ以外を取り除く。「全部逃がす」esc_html() と、「許可したものだけ通す」wp_kses_post() は、出力するHTMLの自由度に応じた使い分けになる。

このブログのテーマではどう使われているか

このブログのテーマ wpmm-blog のファイルを確認したところ、エスケープ関数は次のような箇所で使われていた(出現回数は2026年9月時点のテーマ内の集計)。

関数 テーマ内の出現数 使われ方の例
esc_html() 約70回 ページタイトルや見出しなど、タグの間に置くテキスト
esc_url() 約47回 ファビコンや画像、リンク先のURL
esc_attr() 約29回 属性値(アクセス解析のID等)
esc_js() 1回 インラインスクリプト内の文字列
wp_kses_post() 1回 アーカイブの説明文(HTMLの一部を許可)

検索結果ページの見出しは、実例として分かりやすい。index.php では、検索キーワードを表示する際に esc_html( get_search_query() ) のように、外部由来の値であるキーワードを出力の直前に包んでいる。先ほどの悪い例と違い、キーワードに < などが含まれていても、画面上では単なる文字として表示される。

また、JSON-LD(構造化データ)の出力には、別のエスケープ関数ではなく wp_json_encode() が使われている。JSONとして正しく出力するという別の出力先に合わせた選択で、「出力先ごとに選ぶ」という原則は、ここでも同じ形で現れている。

覚えておきたい判断の順番

エスケープ関数に迷ったときは、次の順で考えるとよい。

確認すること 選ぶもの
その値は、HTMLのどこに置かれるか 出力先に対応する関数(テキスト→esc_html、属性→esc_attr、URL→esc_url)
値の一部にHTMLを残したいか wp_kses_post() のように許可リスト方式の関数
いつ変換するか 保存時ではなく、出力する直前

まとめ

エスケープ関数は、「値」と「HTMLの構造」の境界を出力時に明確に保つための道具である。関数が複数あるのは出力先ごとに境界が違うからであり、出力の直前に行うのは、同じ値が複数の場所で使われうるからである。テーマやプラグインのコードを書く・読むときは、echo している値が外部由来かどうかにかかわらず、「この値は今どこに置かれていて、対応する関数で包まれているか」を確認する習慣をつけておくとよい。前回の $wpdb->prepare() の記事とあわせて読むと、「入力値を信用しない」という考え方が、SQLとHTMLという2つの出力先でそれぞれ形を変えて現れていることが見えてくる。