2026年5月20日

2026年5月20日

WordPressのXSS(クロスサイトスクリプティング)対策をする方法

はじめに

XSS(クロスサイトスクリプティング)は、攻撃者が悪意のあるスクリプトをWebページに埋め込み、訪問者のブラウザ上で実行させる攻撃です。WordPressでは適切な出力エスケープ・入力検証・CSPヘッダーを組み合わせることで防御できます。

症状・原因

  • コメントやフォームにを入力したらダイアログが表示された
  • セキュリティ診断でXSS脆弱性が指摘された
  • プラグインやテーマのカスタマイズでecho $_GET['param']のような安全でない出力をしている
  • ユーザー入力をエスケープせずにHTMLに出力している

解決手順

ステップ1:XSS の脆弱なパターンを検出する

# 安全でない出力パターンを検索
grep -rn "echo \$_GET\|echo \$_POST\|echo \$_REQUEST\|echo \$_COOKIE" /var/www/html/wp-content/

# エスケープなしの変数出力を検索
grep -rn "echo \$[a-zA-Z]" /var/www/html/wp-content/themes/
grep -rn "echo \$[a-zA-Z]" /var/www/html/wp-content/plugins/

# esc_ 関数を使っていない箇所を特定(WP-CLI 使用)
wp eval-file /dev/stdin << 'EOF'
// 現在のテーマの PHP ファイルをスキャン
$theme_dir = get_template_directory();
$files = glob($theme_dir . '/**/*.php');
foreach ($files as $file) {
    $content = file_get_contents($file);
    if (preg_match('/echo\s+\$(?!.*esc_)/m', $content)) {
        echo basename($file) . ': 要確認' . PHP_EOL;
    }
}
EOF

ステップ2:WordPress の出力エスケープ関数を使う

// functions.php / テーマファイル: 適切なエスケープ関数の使い方

// ① HTML テキストのエスケープ(最も一般的)
// 悪い例
echo $user_input;
echo get_query_var('search');

// 良い例
echo esc_html($user_input);
echo esc_html(get_query_var('search'));

// ② HTML 属性値のエスケープ
// 悪い例
echo '<input value="' . $value . '">';

// 良い例
echo '<input value="' . esc_attr($value) . '">';

// ③ URL のエスケープ
// 悪い例
echo '<a href="' . $url . '">';

// 良い例
echo '<a href="' . esc_url($url) . '">';

// ④ JavaScript 内の出力
// 悪い例
echo '<script>var name = "' . $name . '";</script>';

// 良い例
echo '<script>var name = ' . wp_json_encode($name) . ';</script>';

// ⑤ SQL クエリのエスケープ(XSSではなくSQLi対策だが重要)
global $wpdb;
// 悪い例
$wpdb->query("SELECT * FROM wp_posts WHERE ID = " . $_GET['id']);

// 良い例
$wpdb->get_row($wpdb->prepare("SELECT * FROM wp_posts WHERE ID = %d", $_GET['id']));

ステップ3:wp_kses で許可するHTMLタグを制限する

// functions.php: wp_kses でユーザー入力のHTMLを安全にフィルタリング

// ① コメントや投稿本文に許可するタグを定義
$allowed_html = [
    'a'      => ['href' => [], 'title' => [], 'target' => ['_blank']],
    'br'     => [],
    'em'     => [],
    'strong' => [],
    'p'      => ['class' => []],
    'ul'     => [], 'ol' => [], 'li' => [],
    'blockquote' => ['cite' => []],
    'code'   => [],
];

// ② ユーザー入力を安全にフィルタリング
$safe_content = wp_kses($user_content, $allowed_html);
echo $safe_content;

// ③ 完全にテキストのみ許可する場合
$safe_text = wp_strip_all_tags($user_content);
echo esc_html($safe_text);

// ④ フォーム送信時の nonce 検証(CSRF + XSS の複合対策)
// フォームに nonce を追加
wp_nonce_field('my_action', 'my_nonce');

// 送信時に検証
if (!isset($_POST['my_nonce']) || !wp_verify_nonce($_POST['my_nonce'], 'my_action')) {
    wp_die('不正なリクエストです。');
}

ステップ4:コメントのXSS対策を強化する

// functions.php: コメントの XSS 対策

// ① コメント内のHTMLを制限(WordPress デフォルトより厳格に)
add_filter('pre_comment_content', function(string $content): string {
    // スクリプトタグを完全に除去
    $content = wp_strip_all_tags($content, false);
    // または wp_kses で許可タグを制限
    return $content;
});

// ② コメント欄でのHTMLを完全無効化
add_filter('comment_text', function(string $comment_text): string {
    // リンクのみ許可してそれ以外のHTMLを除去
    $allowed = ['a' => ['href' => [], 'rel' => []]];
    return wp_kses($comment_text, $allowed);
});

// ③ Stored XSS: 投稿保存時にサニタイズ
add_filter('content_save_pre', function(string $content): string {
    // 管理者・編集者以外はスクリプトタグを除去
    if (!current_user_can('unfiltered_html')) {
        $content = wp_kses_post($content);
    }
    return $content;
});

ステップ5:CSP ヘッダーと X-XSS-Protection で二重防御する

// functions.php: ヘッダーベースのXSS防御

add_action('send_headers', function(): void {
    // ① X-XSS-Protection(旧ブラウザ向け)
    header('X-XSS-Protection: 1; mode=block');

    // ② CSP で インラインスクリプトを制限(最も強力)
    // まず Report-Only でテスト
    $csp = "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none'";
    header('Content-Security-Policy: ' . $csp);

    // ③ X-Content-Type-Options: MIME スニッフィングを防ぐ
    header('X-Content-Type-Options: nosniff');
});
# 設定後の確認
curl -sI https://example.com/ | grep -iE "x-xss|content-security|x-content-type"

# XSS テスト(開発環境のみ)
curl -s "https://dev.example.com/?s=<script>alert(1)</script>" | grep -i "script"
# エスケープされていれば &lt;script&gt; と表示される

注意事項

  • esc_html()<><>に変換するため、HTMLタグを含む内容には使えません。HTMLタグを許可する場合はwp_kses()またはwp_kses_post()を使用してください
  • unfiltered_html権限を持つ管理者・編集者はスクリプトタグを含む投稿が可能です。これはWordPressの仕様ですが、信頼できないユーザーに編集者権限を与えないよう注意してください
  • wp_json_encode()はJavaScriptに変数を渡す際に使用します。json_encode()とは異なりUnicode文字をエスケープします

まとめ

XSS対策は①grep -rn "echo \$_GET"で脆弱なパターンを検出、②esc_html()esc_attr()esc_url()wp_json_encode()を用途に応じて使い分け、③wp_kses()で許可するHTMLタグを制限、④フォームにはwp_nonce_field()でCSRFトークンを追加、⑤X-XSS-ProtectionとCSPヘッダーで二重防御します。

お気軽にご相談ください

お見積りへ お問い合わせへ