Cookie プラグインと画像アップロードで「XSS攻撃になりかけた」話と、最終的に選んだ安全な実装方法

Web サイトを運営していると、思わぬところにセキュリティの落とし穴が潜んでいます。

今回は、私自身が実際に体験した 「Cookie プラグイン」と「貼り付けた画像」 による XSS(クロスサイトスクリプティング)リスクについてまとめます。

最終的には、functions.php だけで安全に Cookie 同意バナーを実装し、GTM を同意後のみ発火させる方法に落ち着きました。

同じように悩んでいる方の参考になれば幸いです。

1. Cookie プラグインで起きた問題

最初は、一般的な Cookie 同意プラグインを使っていました。

しかし、ある日ふと気づいたのが、

• プラグインが読み込む外部スクリプト

• プラグインが生成する HTML

• プラグインが扱う Cookie の値

これらが XSS の入口になり得るという点でした。

特に、ユーザーが入力した値をそのまま HTML に差し込むタイプのプラグインは、

設定次第で簡単に XSS の温床になります。

「便利だから」と安易に導入したプラグインが、

実はセキュリティリスクを増やしていたわけです。

2. 貼り付けた画像が XSS になりかけた話

もうひとつの問題は「画像アップロード」です。

通常、画像ファイル(JPEG/PNG)は JavaScript を実行できません。

しかし、SVG だけは別です。

SVG は XML 形式なので、

中に <script> を埋め込むことができ、

悪意ある SVG をアップロードすると XSS が成立します。

私が遭遇したのは、

「画像として貼り付けたファイルが実は SVG で、

ブラウザがその中のスクリプトを解釈しようとした」というケースでした。

WordPress はデフォルトで SVG を禁止していますが、

テーマやプラグインによっては許可されてしまうことがあります。

3. プラグインをやめて、functions.php だけで実装することにした

Cookie プラグインのリスクと、画像アップロードの XSS 問題を経験した結果、

私は プラグインを使わず、自分で安全な Cookie 同意バナーを実装するという選択をしました。

目的はシンプルです。

• 同意しないユーザーは追跡しない

• 同意したユーザーだけ GTM を読み込む

• サイト閲覧自体はブロックしない(UX・SEO の観点)

• 外部プラグインに依存しない

• XSS の入口を増やさない

この方針に沿って、functions.php に必要最低限のコードだけを追加しました。

4. 実際に使っている安全な Cookie 同意バナー(functions.php のみ)

以下は、私が現在使っている 画面右下に固定表示される Cookie 同意ボタンです。

同意後にだけ GTM が読み込まれます。

add_action('wp_footer', function() {
    ?>
    <style>
        #consent-btn {
            position: fixed;
            bottom: 20px;
            right: 20px;
            padding: 12px 20px;
            background: #0073aa;
            color: #fff;
            border: none;
            border-radius: 6px;
            font-size: 16px;
            cursor: pointer;
            z-index: 99999;
        }
    </style>

    <?php if (!isset($_COOKIE['cookie_consent'])) : ?>
        <button id="consent-btn">同意してサイトを利用する</button>
    <?php endif; ?>

    <script>
    document.addEventListener('DOMContentLoaded', function() {
        var btn = document.getElementById('consent-btn');
        if (btn) {
            btn.addEventListener('click', function() {
                localStorage.setItem('cookie_consent', 'true');
                document.cookie = "cookie_consent=true; path=/;";
                location.reload();
            });
        }

        if (localStorage.getItem('cookie_consent') === 'true') {
            var gtm = document.createElement('script');
            gtm.async = true;
            gtm.src = "https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXXX";
            document.head.appendChild(gtm);
        }
    });
    </script>
    <?php
});

この実装のポイント

  • プラグイン不要
  • 外部入力を HTML に挿入しないため XSS に強い
  • 同意しない限り GTM は動かない
  • サイト閲覧はブロックしない(一般的な仕様)
  • テーマ構造に依存しない

  1. なぜ「同意しなくてもサイトを読める」方式にしたのか

Cookie 同意バナーには大きく2種類あります。

種類 動作 メリット
一般的な方式 同意しなくてもサイトは読める UXが良い、SEOに影響しない、法律的に正しい
ブロッカー型 同意するまでサイトを読めない 法的に強いが、離脱率が高い

私は 一般的な方式 を採用しました。

理由は:

  • GDPR でも「サイト閲覧自体は同意不要」
  • Google はブロッカー型を嫌う(SEOに悪影響)
  • ユーザー体験が悪くなる
  • 私のサイトは広告目的ではない

つまり、最もバランスが良い方式です。


  1. XSS を防ぐために気をつけていること

今回の経験から、私は次の点を徹底するようになりました。

  • 不要な Cookie プラグインは使わない
  • SVG をアップロードしない
  • 外部入力を innerHTML に入れない
  • テーマやプラグインのアップローダーを信用しすぎない
  • functions.php で必要最低限のコードだけを書く

特に SVG は本当に危険で、
「画像だから安全」と思っていると痛い目に遭います。


  1. まとめ

今回の経験で学んだのは、

  • 便利なプラグインほど XSS の入口になりやすい
  • 画像アップロードも安全とは限らない(特に SVG)
  • Cookie 同意は自作したほうが安全で軽い
  • 同意しないユーザーは追跡しないのが正しい
  • functions.php だけで十分実装できる

ということでした。

同じように悩んでいる方の参考になれば嬉しいです。


参考

子テーマ、style.css、functions.php

サーバー
↓
コントロールパネル
↓
サイト名
↓
ftpファイルマネージャー
↓
wp-content
↓
themes
↓
新規ディレクトリ(新規フォルダ)子テーマ作成
yourtheme-child
↓
新規ファイル(2つ作成)
style.css
functions.php
↓

※親テーマにはクラシックかブロックがあるが、子テーマにfooter.phpを作らなくても仕上がる


style.css

/*
 Theme Name:   子テーマ(例:aaaaa-child)
 Template:     親テーマ(例:aaaaa)
*/

※WordPressのテーマ、テーマのバージョンアップに連動する書き換え用(子)テーマとして-childを追加


functions.php

<?php

// 親テーマのCSSを読み込む
add_action( 'wp_enqueue_scripts', function() {
    wp_enqueue_style( 'parent-style', get_template_directory_uri() . '/style.css' );
});


// Cookie 同意ボタンと GTM の制御
add_action('wp_footer', function() {
    ?>
    <style>
        #consent-btn {
            position: fixed;
            bottom: 20px;
            right: 20px;
            padding: 12px 20px;
            background: #0073aa;
            color: #fff;
            border: none;
            border-radius: 6px;
            font-size: 16px;
            cursor: pointer;
            z-index: 99999;
        }
    </style>

    <?php if (!isset($_COOKIE['cookie_consent'])) : ?>
        <button id="consent-btn">同意してサイトを利用する</button>
    <?php endif; ?>

    <script>
    document.addEventListener('DOMContentLoaded', function() {
        var btn = document.getElementById('consent-btn');
        if (btn) {
            btn.addEventListener('click', function() {
                localStorage.setItem('cookie_consent', 'true');
                document.cookie = "cookie_consent=true; path=/;";
                location.reload();
            });
        }

        if (localStorage.getItem('cookie_consent') === 'true') {
            var gtm = document.createElement('script');
            gtm.async = true;
            gtm.src = "https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXXX";
            document.head.appendChild(gtm);
        }
    });
    </script>
    <?php
});

※https://www.googletagmanager.comを検索、GTM-XXXXXXXはコンテナIDのGTM-XXXXXXXのこと