WordPress開発におけるセキュリティ原則の最も基本となる合言葉が「Sanitize on Input, Escape on Output(入力時に無害化し、出力時にエスケープする)」です。
しかし、実際の開発現場では「とりあえず sanitize_text_field() を使っておけば安全」と考え、複数行テキストの改行が消えてしまったり、リッチテキストのHTMLタグが全削除されてしまったり、あるいは出力時エスケープを怠ってXSS(クロスサイトスクリプティング)脆弱性を作り出してしまう事例が非常に多く見られます。
💡 本記事は「WordPress セキュアコーディング実践シリーズ」の第4弾です
- 第1弾:WordPressのnonceを理解しセキュアな機能開発へステップアップ(CSRF対策の基礎)
- 第2弾:WordPress REST APIの認証・権限管理入門(wp_rest_nonce & permission_callback)
- 第3弾:current_user_can()によるWordPress権限チェック実践(認可・Capability設計)
- 第4弾(本記事):sanitize_text_fieldとwp_ksesの使い分け完全ガイド(入力無害化・XSS対策)
本記事では、WordPressの代表的なサニタイズ関数 sanitize_text_field() と wp_kses() / wp_kses_post() の動作の違い、適切な使い分け、その他の特化サニタイズ関数、そして出力時エスケープ関数との正しい連携パターンを徹底解説します。
1. サニタイズ(入力無害化)とエスケープ(出力無害化)の違い
WordPressにおけるデータセキュリティは、「2段階の防御」によって成立しています。
| 段階 | 用語 | 実行タイミング | 主な目的 | 代表的関数 |
|---|---|---|---|---|
| 第1段階 | サニタイズ(Sanitizing) | データベース保存直前(POST受信時) | 不正な文字・意図しないタグ・危険なオクテットの除去、型の正規化 | sanitize_text_field(), wp_kses_post() |
| 第2段階 | エスケープ(Escaping) | HTML/属性/JS出力直前(Late Escaping) | ブラウザでの不正スクリプト実行(XSS)の防止 | esc_html(), esc_attr(), esc_url() |
「DBに保存する時にサニタイズしたから、出力時は生で echo してOK」という考えは重大なアンチパターンです。DB内のデータが常に安全とは限らない(管理画面直接操作や別処理の混入)ため、出力直前のエスケープ(Late Escaping)は絶対に省略してはなりません。
2. sanitize_text_field() の動作仕様と使いどころ
sanitize_text_field( string $str ) は、ユーザー入力から「プレーンな1行文字列」を抽出するための最も標準的な関数です。
sanitize_text_field() が実行する処理
- 無効なUTF-8文字のチェックと除去
- すべてのHTMLタグ(
<script>,<b>,<a>等)の完全削除(strip_tags相当) - 改行コード(\r, \n)およびタブ(\t)の除去
- 連続する半角空白の圧縮・前後の余白トリム
- オクテット(URLエンコードされた文字)の安全化
適切なユースケース vs 不適切なユースケース
| 用途区分 | 入力項目例 | 使用可否と推奨関数 |
|---|---|---|
| 適切 ⭕ | 氏名、ユーザー名、電話番号、郵便番号、単一行のタイトル・見出し、短い設定値 | sanitize_text_field() |
| 不適切 ❌ | 問い合わせフォームの本文、備考欄、複数行のメッセージ | sanitize_textarea_field()(改行を保持) |
| 不適切 ❌ | ブログ本文、HTML装飾を含むリッチテキスト、埋め込みコード | wp_kses_post() / wp_kses() |
| 不適切 ❌ | メールアドレス、URL、ファイル名 | 専用の sanitize_email(), esc_url_raw() 等 |
3. wp_kses() と wp_kses_post() による安全なHTML許可制御
ユーザーや管理者にHTMLタグ(リンク、太字、リスト、画像など)の入力を許可したい場合、sanitize_text_field() を使うとすべてのタグが消えてしまいます。そこで使用するのが KSES(HTML無害化ライブラリ) です。
① wp_kses_post():一般的な投稿本文向け
WordPressの通常投稿本文で許可されている標準HTMLタグ(<p>, <a>, <img>, <strong>, <ul> 等)をすべて安全に通し、悪意のある <script> や onload などの危険な属性・イベントハンドラだけを除去します。
// リッチテキストエディタの入力を保存する場合
$safe_html = wp_kses_post($_POST['rich_description']);
update_post_meta($post_id, '_custom_rich_desc', $safe_html);
② wp_kses():許可タグを明示的に指定する場合
「リンク(<a>)と太字(<strong>)だけを許可し、画像やテーブルは禁止したい」など、厳格なホワイトリスト制御を行う場合は wp_kses( $string, $allowed_html ) を使用します。
<?php
// 許可するタグと属性のホワイトリスト配列を定義
$allowed_html = array(
'a' => array(
'href' => array(),
'title' => array(),
'target' => array(),
'rel' => array(),
),
'strong' => array(),
'em' => array(),
'span' => array(
'class' => array(),
),
);
$user_bio = wp_kses($_POST['user_bio_html'], $allowed_html);
update_user_meta($user_id, 'description_html', $user_bio);
4. WordPress標準サニタイズ関数チートシート
| データ種別 | サニタイズ関数 | 主な動作・特徴 |
|---|---|---|
| 1行テキスト | sanitize_text_field() | タグ除去・改行除去・空白トリム |
| 複数行テキスト | sanitize_textarea_field() | タグ除去・改行(\n)は保持 |
| HTML投稿 | wp_kses_post() | 投稿本文標準タグのみ許可 |
| 制限付きHTML | wp_kses() | 指定ホワイトリストタグのみ許可 |
| メールアドレス | sanitize_email() | メール形式に不正な文字を除去 |
| ファイル名 | sanitize_file_name() | 無効文字・パス移動(../)を除去 |
| URL(DB保存用) | esc_url_raw() / sanitize_url() | DB保存用の生URL無害化 |
| キー・スラッグ | sanitize_key() / sanitize_title() | 小文字・英数字・ハイフンに正規化 |
| CSSクラス名 | sanitize_html_class() | 有効なHTMLクラス名に限定 |
| 整数値・ID | absint() / intval() | 正の整数(ID)にキャスト |
5. 出力時エスケープ関数との連携(Late Escapingの実践)
画面にデータを出力する際は、コンテキスト(HTML本文、HTML属性、URL、JavaScript内)に合わせて適切なエスケープ関数を選択します。
<!-- 1. 通常のテキスト出力 -->
<p><?php echo esc_html( $user_name ); ?></p>
<!-- 2. HTML属性内(value, title, placeholder等)への出力 -->
<input type="text" name="title" value="<?php echo esc_attr( $post_title ); ?>">
<!-- 3. リンク先URL(href, src等)への出力 -->
<a href="<?php echo esc_url( $external_link ); ?>">リンク</a>
<!-- 4. JavaScript変数への埋め込み -->
<script>
const config = <?php echo wp_json_encode( $server_data ); ?>;
</script>
6. 実践コード:受信・サニタイズ・保存・出力の完全フロー
これまでのセキュアコーディングシリーズ(nonce検証・権限チェック・サニタイズ・エスケープ)を統合した完全な実装例です。
<?php
// フォーム保存ハンドラ
add_action('admin_post_save_company_profile', 'handle_save_company_profile');
function handle_save_company_profile() {
// 1. CSRF対策(nonce検証)
check_admin_referer('company_profile_action', 'company_profile_nonce');
// 2. 認可チェック(権限確認)
if ( ! current_user_can('manage_options') ) {
wp_die('アクセス権限がありません。', 403);
}
// 3. 入力値の適切なサニタイズ
$company_name = isset($_POST['company_name']) ? sanitize_text_field($_POST['company_name']) : '';
$company_url = isset($_POST['company_url']) ? esc_url_raw($_POST['company_url']) : '';
$description = isset($_POST['description']) ? wp_kses_post($_POST['description']) : '';
$employee_num = isset($_POST['employees']) ? absint($_POST['employees']) : 0;
// 4. DB保存
update_option('my_company_name', $company_name);
update_option('my_company_url', $company_url);
update_option('my_company_desc', $description);
update_option('my_company_employees', $employee_num);
wp_safe_redirect(add_query_arg('updated', '1', wp_get_referer()));
exit;
}
7. まとめ&セキュアコーディングシリーズ関連記事
サニタイズとエスケープは、WordPressにおけるXSS脆弱性やSQLインジェクション、データ破壊を防ぐための最も重要かつ日常的なコーディング習慣です。
「1行テキストなら sanitize_text_field()」「リッチHTMLなら wp_kses_post() / wp_kses()」「出力時は必ず esc_html() / esc_attr()」の使い分けを徹底し、安全性の高いWordPressプラグイン・テーマ開発を実践しましょう。
📚 開発者向けセキュアコーディング関連記事
- 【シリーズ第1弾】WordPressのnonceを理解しセキュアな機能開発へステップアップ – [CVE-2025-1463]脆弱性解説
フォーム・リンク・AjaxにおけるCSRF防御の基礎と実装手法を解説。 - 【シリーズ第2弾】WordPress REST APIの認証・権限管理入門|wp_rest_nonceとpermission_callbackの実践実装
RESTエンドポイントの安全な公開とCookie認証・認可のベストプラクティス。 - 【シリーズ第3弾】current_user_can()によるWordPress権限チェック実践|脆弱性を防ぐ認可・Capability設計
is_admin()の落とし穴からメタCapabilityを活用した個別リソース認可まで徹底解説。
🛡️ サイトに潜むXSSやセキュリティリスクを無料診断!
エスケープ漏れやプラグインの脆弱性は、サイトの改ざんや情報漏えいに直結します。「MozCheck」は、WordPressサイトのURLを入力するだけで、セキュリティ状態を数分でレポートする無料オンライン診断ツールです。
コメントを残す