WordPress Logo WordPress エスケープ処理

WordPress でデータを無害化 (サニタイズ) する方法について。

更新日:2019年02月03日

作成日:2019年01月17日

エスケープ処理

WordPress のテーマで文字列を echo や print などを使って出力する際は、それらを適切にエスケープ処理する必要があります。PHP には、htmlspecialchars() 関数などのエスケープ処理用の関数がありますが、WordPress にも文字列を適切にエスケープしたり、HTML タグや URL を処理してくれる関数が用意されています。

変数や関数などの値や文字列を出力する場合は、用途によってエスケープやサニタイズの方法を適切に選択してセキュリティ上のリスクを回避する必要があります。

参考サイト:日本語 Codex「データ検証

the_xxxx と get_xxxx

テンプレートタグには、the_title() のように値を出力(echo)するものと、get_the_title() のように出力はせず値を返すだけのものがあります。

the_ を前置するテンプレートタグは、WordPress が echo を行っているテンプレートタグで(適切にエスケープされて出力されるので)安全に使用することができます。そのため、何らかの出力を行う際は(可能であれば) the_xxxx 関数を使用するようにします。

<a href="<?php the_permalink(); ?>">

get_ を前置するテンプレートタグは値を返すテンプレートタグなので、出力する場合は適切にエスケープ処理を行う必要があります。

例えば、前述のサンプル the_permalink() の定義(ソース)を確認すると以下のようになっています。

function the_permalink( $post = 0 ) {
  echo esc_url( apply_filters( 'the_permalink', get_permalink( $post ), $post ) );
}

get_permalink() で取得した値に「the_permalink」フィルターフックを適用して、esc_url()エスケープ処理後に出力されています。

以下は、the_title() と get_permalink() を使ってタイトルにリンクを付けて h2 要素で出力する例です。

the_title() は第一引数と第二引数にタイトルの前後に配置するテキスト(文字列)を指定することができます。この場合、引数に文字列として指定するので the_permalink() は使えず get_permalink() を使いますがエスケープ処理が必要になります。

<?php the_title( '<h2><a href="' . esc_url( get_permalink() ) . '">', '</a></h2>' ); ?>

esc_html

esc_html() は HTML で特別な意味を持つ文字(&、<、>、"、')をエスケープ(エンコード)して &amp; や &lt; のような文字参照(HTMLエンティティ)と呼ばれる表記に変換します。そして esc_html フィルターを通して出力します。

esc_html( $text )

パラメータ
$text(string):(必須) エスケープするテキスト
戻り値
エスケープされた HTML 文字列
<?php 
$html = esc_html( '<a href="http://www.example.com/">A link</a>' );
echo $html;
?>

//出力
&lt;a href="http://www.example.com/"&gt;A link&lt;/a&gt;

上記のようにすると「A link」ではなく、文字列として「'<a href="http://www.example.com/">A link</a>'」と出力されます。

また、この関数は既にエンコードされている場合は二重にエンコードしません(例えば、文字列中に「&lt;」が含まれている場合、この中の「&」をさらに「&amp;」に置換は行いません)。

以下の場合、「 A &amp; B」と出力され、「A &amp;amp; B」とは出力されません。

echo esc_html( 'A &amp; B' );

esc_html() は全てのタグをエスケープしてしまいますが、特定のタグはそのまま出力させたい(特定のタグ以外を除去する)場合は、wp_kses() を使用します。

既にエンコードされている文字を二重にエンコードする必要がある場合は、esc_textarea() を使用します。

esc_attr

esc_attr() は HTML 要素の属性(alt, value, title, class など)の値に出力する文字をエスケープする場合に使用します。

但し、href や src のような URI(URL) を指定可能な属性の場合は、XSS 対策として esc_attr() の代わりに esc_url() を使うようにします。(※ を参照

機能的には esc_html() と同じですが、attribute_escape フィルタを通して出力されます。また esc_html() 同様、既にエンコードされている場合は二重にエンコードしません。

esc_attr( $text )

パラメータ
$text(string):(必須) エスケープするテキスト
戻り値
HTML 文字参照でエンコードされたテキスト
使用例(value 属性の値に出力)
<?php $name = ( isset( $_POST['name'] ) ) ? $_POST['name'] : ''; ?>
<input type="text" name="name" value="<?php echo esc_attr( $name ); ?>">

※ href や src のような URI(URL) を指定可能な属性の場合は、XSS 対策として esc_attr() の代わりに esc_url() を使うようにします。

<!-- 正しい使い方 -->
<img src="<?php echo esc_url( $src ); ?>" />
 
<!-- 問題はないが esc_attr() は冗長で不要 -->
<img src="<?php echo esc_attr( esc_url( $src ) ); ?>" />
  
<!-- 誤った使い方(XSS 脆弱性あり) -->
<img src="<?php echo esc_attr( $src ); ?>" />

以下は、_s(underscores) というスターター・テーマの header.php の記述の一部です。

<a href="<?php echo esc_url( home_url( '/' ) ); ?>" rel="home"><?php bloginfo( 'name' ); ?></a>

また、属性の値に出力する際は、必ずクオート("")で囲む必要があります。

<!-- 正しい使い方 -->
<input type="text" name="name" value="<?php echo esc_attr( $name ); ?>">
 
<!-- 誤った使い方(XSS 脆弱性あり)-->
<input type="text" name="name" value=<?php echo esc_attr( $name ); ?>>

esc_url

esc_url() は URL のプロトコルのチェックや適切でない文字をエスケープ(または除去)して、URL を無害化します。URL を文字列で出力する場合や、URL を指定可能な属性(href や src)に値を出力する場合などで使用します。

この関数はホワイトリストに登録されているプロトコル(デフォルトでは http, https, ftp, ftps, mailto, news, irc, gopher, nntp, feed, and telnet)ではないものは拒絶し、無効なキャラクタを除外し、危険なキャラクタを削除します。「&」と「'」の文字はそれぞれ「&#038」と「&#039」に変換します。

esc_url( $url, $protocols, $_context )

パラメータ
  • $url(string):(必須) 無害化しようとする URL
  • $protocols(array):(optional) 許可するプロトコルの配列(初期値: null)。指定しなければデフォルトで登録されているプロトコルのみ許可されます。
  • $_context (string):(optional) URLをどのように用いるかを指定(初期値: 'display')
戻り値
'clean_url' フィルターと通して無害化された $urlが返されます。もし $protocols に指定されていないプロトコルを $url が用いていた場合、あるいは $url が空であった場合は、空の配列が返されます。

以下は使用例です。

<a href="<?php echo esc_url( home_url( '/' ) ); ?>">Home</a>
<a href="<?php echo esc_url( home_url( '/cat/' ) ); ?>">カテゴリー</a>

home_url() は管理画面「設定」→「一般」の "サイトアドレス (URL)"の値を返す関数で、ホームURLからの相対パスを指定できます。

"サイトアドレス (URL)"が「http://example.com」の場合、以下のように表示されます。

<a href="http://example.com/">Home</a>
<a href="http://example.com/cat/">カテゴリー</a>

また、絶対リンクと想定される文字列にプロトコルが含まれていない場合は、自動で http:// が補完されます(最後の「Link 5」の例)。

<?php
  echo '<a href="'. esc_url("http://example.com/01/?key=value&q's=foo").'">Link 1</a>';
  echo '<a href="'. esc_url("/sample/01/").'">Link 2</a>';
  echo '<a href="'. esc_url("#sample").'">Link 3</a>';
  echo '<a href="http://example.com'. esc_url("/sample/01/").'">Link 4</a>'; 
  echo '<a href="'. esc_url("sample/01/").'">Link 5</a>'; 
?>
出力結果
<a href="http://example.com/01/?key=value&amp;q's=foo">Link 1</a>
<a href="/sample/01/">Link 2</a>
<a href="#sample">Link 3</a>
<a href="http://example.com/sample/01/">Link 4</a>
<a href="http://sample/01/">Link 5</a>

4番目の例の場合、「http://example.com」が自分のサイトのアドレスなら通常は、「home_url()」を使って以下のように記述します。

<?php echo '<a href="'. esc_url( home_url( '/sample/01/') ).'">Link 4</a>'; ?>
          
<!--   出力結果     -->
<a href="http://www.example.com/sample/01/">Category</a>

先頭に / がない場合

以下のように記述した(先頭に / がない)場合、「http://」が自動的に補完されてしまうので(おそらく想定外の出力になるので)注意が必要です。

<?php 
  echo '<a href="http://example.com/'. esc_url("sample/01/").'">Link 6</a>';
  echo '<a href="'. esc_url("../sample/01/").'">Link 7</a>'; 
?>
出力結果
<a href="http://example.com/http://sample/01/">Link 6</a>
<a href="http://../sample/01/">Link 7</a>

登録されていないプロトコル

ホワイトリストに登録されていないプロトコルの場合は、空の文字列が返されます。

<?php
  echo '<a href="'. esc_url("mailto:info@exmple.com").'">Mailto</a>';
  echo '<a href="'. esc_url("tel:0123456789").'">TEL</a>';
  echo '<a href="'. esc_url("skype:abcd.efgh?call").'">Skype</a>';
  echo '<a href="'. esc_url("macappstores://itunes.apple.com/").'">Mac App Store</a>';          
?>
出力結果(tel: は許容されるようです)
<a href="mailto:info@exmple.com">Mailto</a>
<a href="tel:0123456789">TEL</a>
<a href="">Skype</a>
<a href="">Mac App Store</a>
プロトコルの追加

デフォルトで登録されていないプロトコルを使用すると、空の文字列が返されます。

<?php echo '<a href="'. esc_url("skype:abcd.efgh?call").'">Call Skype</a>'; ?>
出力結果
<a href="">Call Skype</a>

第二引数に許可するプロトコルの配列を指定できます。以下の場合は、skype プロトコルのみが許可されます。

echo '<a href="'. esc_url("skype:abcd.efgh?call" , array('skype')).'">Skype</a>';    

以下は、デフォルトのプロトコルに「skype」と「macappstores」を追加した配列を指定する例です。(配列に許可したいプロトコルを指定することができます)

<?php
  $protocols = array('skype', 'macappstores', 'http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet');
  echo '<a href="'. esc_url("skype:abcd.efgh?call" , $protocols).'">Skype</a>';
  echo '<a href="'. esc_url("macappstores://itunes.apple.com/" , $protocols).'">Mac App Store</a>'; 
?>

または、以下のようにデフォルトのプロトコルを拡張することもできます。

デフォルトのプロトコルの拡張

以下を functions.php に記述することで、デフォルトのプロトコルを拡張することができます。こうすれば、第二引数にプロトコルを指定しなくても追加したプロトコルが許可されます。

/**
 * 許可されているプロトコルを拡張
 * パラメータの $protocols :WordPress で許可されているデフォルトのプロトコルのリスト(配列)
 * return する $protocols:新しいプロトコルと追加して更新されたプロトコルのリスト(配列)
 */
function my_extend_allowed_protocols( $protocols ){
    $protocols[] = 'skype';
    $protocols[] = 'spotify';
    $protocols[] = 'macappstores';
    return $protocols;
}
add_filter( 'kses_allowed_protocols' , 'my_extend_allowed_protocols' );

Code Reference/esc_url/User Contributed Notes

esc_url_raw

esc_url_raw() は URL をデータベースに格納するときに使う関数です。この関数は文字を HTML 実体にエンコードしません(それ以外は、esc_url() と同じ)。URL を保存するときやエンコードしない形で保存したいときはこの関数を使います。

esc_url_raw( $url, $protocols )

パラメータ
  • $url(string):(必須) 無害化しようとする URL
  • $protocols(array):(optional) 許可するプロトコルの配列(初期値: null)。指定しなければデフォルトで登録されているプロトコルのみ許可されます。
戻り値
無害化された $url が返されます(文字を HTML 実体にエンコードしません)。

urlencode

urlencode() は文字列を URL エンコードします(PHP 関数)。クエリパラメータで使用する場合や URL に変数や関数が返す値を含めたりする場合は、URL エンコードします。

urlencode ( $str )

パラメータ
$st(string):エンコードする文字列
戻り値
「-」「_」「.」を除くすべての非英数文字が % 記号 (%)に続く二桁の数字で置き換えられ、 空白は + 記号(+)にエンコードされます。
<?php 
  $link = 'リンクパス';
  echo '<a href="'. esc_url('http://example.com/'). urlencode( $link ) . '/">Link</a>';
?>

出力結果(日本語などのマルチバイト文字は、以下のように URL エンコードされます)

<a href="http://example.com/%E3%83%AA%E3%83%B3%E3%82%AF%E3%83%91%E3%82%B9/">Link</a>

urlencode_deep

urlencode_deep() は配列のすべての要素を URL エンコードします。

urlencode_deep( $value )

パラメータ
$value(mixed):エンコードする配列または文字列
戻り値
エンコードされた配列または文字列

esc_textarea

esc_textarea() は textarea 要素の中で使えるように文字列をエンコード(エスケープ)する関数です。HTML で特別な意味を持つ文字(&、<、>、"、' )をエンコードし、textarea 以外でも使用できます。

esc_html() とは異なり、既にエンコードされている文字を二重にエンコードします。

また、無効な UTF-8 文字列はチェックしません。無効な UTF-8 文字列をチェックする必要がある場合は、sanitize_text_field() を使用することができます。

esc_textarea( $text )

パラメータ
$text(string):(必須) エンコード(エスケープ)するテキスト
戻り値
HTML エンティティをエンコード済みのテキスト。
<?php 
  $text = '<a href="about.html">';
?>
<textarea><?php echo esc_textarea( $text ); ?></textarea>

出力結果

<textarea>&lt;a href=&quot;about.html&quot;&gt;</textarea>

既にエンコードされている文字を二重にエンコードします。

<?php 
  $text = '&amp;';
?>
<textarea><?php echo esc_textarea( $text ); ?></textarea>

出力結果

<textarea>&amp;amp;</textarea>

タグやスクリプトの除去

PHP には、文字列から HTML および PHP タグを削除してくれる strip_tags() 関数がありますが、WordPress にも以下のようなタグやスクリプトを削除する便利な関数が用意されています。

wp_strip_all_tags

wp_strip_all_tags() は指定された文字列から、<script> と <style> を含むあらゆる HTML タグを適切に除去します。

wp_strip_all_tags( $string, $remove_breaks )

パラメータ
  • $string(string ):(必須) HTML タグを含む文字列
  • $remove_breaks(boolean):(オプション) 余分な改行と空白を削除するかどうかの真偽値。true にすると、1 つ以上の連続する改行または空白(タブを含む)を半角スペース 1 つに置換します。(初期値: false)
戻り値
処理済みの文字列
<?php 
  $text = '<a href="about.html">リンク</a>
  <script>alert("hello");</script>
  <style>a {color:red}</style>';
?> 
<p><?php echo wp_strip_all_tags( $text ); ?></p>

出力結果(タグが全て取り除かれます)

<p>リンク</p>

sanitize_text_field

sanitize_text_field() はユーザーからの入力やデータベースから取得した文字を無害化(以下)します。

  • 無効な UTF-8 をチェック
  • 独立した '<' 文字をエンティティーへ変換
  • タグをすべて除去
  • 改行・タブ・余分な空白を削除
  • オクテット('%' に続く 2 桁の 16 進数)を除去

sanitize_text_field( $str )

パラメータ
$str(string ):(必須) 無害化される文字列
戻り値
無害化された文字列
<?php 
  $text = '<a href="about.html">リンク</a>
  <p>タグは<strong>全て削除</strong>されます。</p>
  <script>alert("hellow");</script>
  <これはタグではありませんが削除されます>
  <<  >>';
?> 
<textarea><?php echo sanitize_text_field( $text ); ?></textarea>

出力結果

<textarea>リンク タグは全て削除されます。 &lt;&lt; &gt;&gt;</textarea>

wp_kses

wp_kses() は指定した特定の HTML タグ以外を除去します。

KSES は悪意あるスクリプトを除去します。すべての信頼できない HTML (投稿文、コメント文など) は wp_kses() を通すべきです。

--日本語Codex データ検証より抜粋--

KSES は「KSES Strips Evil Scripts」の略です(Function Reference/wp kses

wp_kses( $string, $allowed_html, $allowed_protocols )

パラメータ
  • $string(文字列):対象の文字列(必須)
  • $allowed_html(配列):許可するタグ(HTML 要素)とその属性(必須)
  • $allowed_protocols(配列):許可するプロトコル(オプション)
戻り値
許可した以外のタグや属性を全て取り除いた文字列

第一引数に対象の文字列、第二引数に許可するタグとその属性を配列で指定します。以下は許可するタグと属性を配列で個々に指定する例です。

<?php 
// 第二引数の指定例
$allowed_html = array(
  'a' => array(
    // 属性を残す場合はその属性を指定します
    'href' => array (),
    'target' => array()
  ),
  'br' => array(),
  'strong' => array(), 
);
$text = '<a href="example.com" target="_blank" title="link">Link</a><br>改行
<p><strong><span>パラ</span>グラフ</strong></p>
<script>alert("hellow");</script><style> a {color:red;}</style>';
?> 
<div><?php echo wp_kses($text, $allowed_html); ?></div>

出力結果(許可した以外のタグや属性が全て取り除かれます)

<div><a href="example.com" target="_blank">Link</a><br>改行
<strong>パラグラフ</strong>
alert("hellow"); a {color:red;}</div>

何度も使用する場合は、第二引数を functions.php に記述しておくと便利です。

//functions.php
$allowed_html = array(
  'a' => array(
    'href' => array (),
    'target' => array()
  ),
  'br' => array(),
  'strong' => array(), 
);

第二引数には、wp_kses_allowed_html() を使って WordPress で用意している許可するタグや属性のリストを指定することができます。

post を指定すると、投稿ページで許可されているタグが許可されます。

<?php echo wp_kses($text, wp_kses_allowed_html( 'post' )); ?>

wp_kses_post()

wp_kses_post() を使用すれば、投稿コンテンツに対して許可された HTML タグに基づいて、与えられたコンテンツをサニタイズ(無害化)します。

wp_kses_post( $data )

パラメータ
$data (string):(必須) フィルターする投稿コンテンツ(文字列)
戻り値
フィルターされた投稿コンテンツ(文字列)。許可された HTML タグと属性はそのままにされます。
<?php 
  $text = '<a href="example.com" target="_blank" title="link">Link</a><br>改行
  <p><strong><span>パラ</span>グラフ</strong></p>
  <script>alert("hellow");</script><style> a {color:red;}</style>';
?> 
<div><?php echo wp_kses_post($text); ?></div>
出力結果
<div><a href="example.com" target="_blank" title="link">Link</a><br>改行
<p><strong><span>パラ</span>グラフ</strong></p>
alert("hellow"); a {color:red;}</div>

wp_kses($text, wp_kses_allowed_html( 'post' )) や wp_kses( $text, 'post' ) と指定するのと同等のようです。

wp-includes/kses.php
function wp_kses_post( $data ) {
    return wp_kses( $data, 'post' );
}

wp_kses_data()

wp_kses_data() を用いれば、コメントで許可される少数のタグのみを残すことができます。

wp_kses_data( $data )

パラメータ
$data (string):(必須) フィルターするコンテンツ(文字列)
戻り値
HTML kses ルールを使用してサニタイズ(無害化)されたコンテンツ(文字列)
<?php 
$text = '<a href="example.com" target="_blank" title="link">Link</a><br>改行
<p><strong><span>パラ</span>グラフ</strong></p>
<script>alert("hellow");</script><style> a {color:red;}</style>';
?> 
<div><?php echo wp_kses_data($text); ?></div>
出力結果
<div><a href="example.com" title="link">Link</a>改行
<strong>パラグラフ</strong>
alert("hellow"); a {color:red;}</div>

wp_kses($text, wp_kses_allowed_html('data')) と指定するのと同等のようです。

wp_kses_allowed_html

wp_kses_allowed_html() は指定されたコンテキストに合う「許可するタグやその属性のリスト」を返す関数です。

パラメータ
$context(string|array):(オプション)タグを指定するためのコンテキスト。指定できる値は post、strip、entities、data または user_description、pre_user_description などの「field filter 名」です。( wp-includes/kses.php で関数の定義部分の switch ( $context ) に記述されています)
戻り値
コンテキストに合わせた許可するタグのリスト。タグ名をキーとして、属性名を値とする多次元配列。

以下はコンテキストに post を指定してその内容を var_dump() で確認する例です。

<?php
$allowed_tags = wp_kses_allowed_html( 'post' );
var_dump( $allowed_tags );
?>

/* 出力例
array(74) {
  ["address"]=>array(11) {
    ["aria-describedby"]=>bool(true)
    ["aria-details"]=>bool(true)
    ["aria-label"]=>bool(true)
    ["aria-labelledby"]=>bool(true)
    ["aria-hidden"]=>bool(true)
    ["class"]=>bool(true)
    ["id"]=>bool(true)
    ・・・中略・・・
  ["video"]=>array(20) {
    ["autoplay"]=>bool(true)
    ["controls"]=>bool(true)
    ["height"]=>bool(true)
  ・・・中略・・・
}
*/

以下は、wp_kses() の第二引数に wp_kses_allowed_html( 'data' ) を使って許可するタグを指定する例です。

<?php echo wp_kses($text, wp_kses_allowed_html( 'data' )); ?>

以下は wp_kses_allowed_html( 'data' ) で許可されるタグと属性を出力して確認する例です。

<?php
  $allowed_tags = wp_kses_allowed_html( 'data' );
  var_dump( $allowed_tags );
?>

/* 出力結果
  array(14) {
    ["a"]=> array(2) {
      ["href"]=>bool(true)
      ["title"]=>bool(true)
    }
    ["abbr"]=>array(1) {
      ["title"]=>bool(true)
    }
    ["acronym"]=>array(1) {
      ["title"]=>bool(true)
    }
    ["b"]=>array(0) {}
    ["blockquote"]=>array(1) {
      ["cite"]=>bool(true)
    }
    ["cite"]=>array(0) {}
    ["code"]=>array(0) {}
    ["del"]=>array(1) {
      ["datetime"]=>bool(true)
    }
    ["em"]=>array(0) {}
    ["i"]=>array(0) {}
    ["q"]=>array(1) {
      ["cite"]=>bool(true)
    }
    ["s"]=>array(0) {}
    ["strike"]=>array(0) {}
    ["strong"]=>array(0) {}
  }
*/