WordPressのインラインJavaScriptをなくして、Content Security Policyを設定

うちのテーマは今のこのデザインになるまでは、サードパーティーのテーマを使っていたんです。
で、そのテーマには脆弱性があってXSSが有効だったんですよー((゜Д゜;))

今は1から作った独自のテーマを使っていて、汎用的な拡張性(テーマに必要な画像等のアップロード機能やAJAX)や外部を参照する不明なコードはありません。
なのでテーマ経由の脆弱性はない感じなのです。

だけれど、XSSを防ぐセキュリティ関連のヘッダーを設定しておいた方が良いって事なのでApacheにヘッダーを追加してみました。
新しく追加したのはX-Content-Type-Options、X-XSS-Protection、Content-Security-Policyの3つで、どれもXSSを防ぐのに効果が高いもの。
X-Frame-Optionsだけはブログをiframeで表示されていたサイトがあったので設定済みでしたw

設定内容はこんな感じ。

Header always set X-Frame-Options SAMEORIGIN
Header always set X-Content-Type-Options nosniff
Header always set X-XSS-Protection "1; mode=block"
Header always set Content-Security-Policy "default-src 'self' *.wolfs.jp; script-src 'self' *.wolfs.jp; child-src 'self' data: www.youtube.com; style-src 'self' 'unsafe-inline' *.wolfs.jp; img-src 'self' data: *.wolfs.jp *.gravatar.com;"

 
で、今回躓いたのはContent Security Policy(CSP)の設定。

JavaScriptやCSSは以前から1つにまとめているので大した問題ではなかったのですが、CSPで「script-src 'self' *.wolfs.jp」のようにインラインスクリプトを許可しない設定だとWordPressが出力するコメント関連のスクリプトが動かなくなっちゃう。
かと言って許可するとXSSに対して弱くなり、CSPを設定しないのとあまり変わらないような結果になります。

うちの環境で見た感じ、主にWordPressが出力するインラインJavaScriptは、
 ・コメントフォームの隠しフィールド「_wp_unfiltered_html_comment_」
 ・コメントのある「返信」ボタンのonclick
のようです。

とりあえず、その辺を無効化して無効化した物を再度使用可能にするように作っていきます。

まずはWordPressの機能変更のPHPとJavaScriptコード。
PHP

<?php
 
class wp_csp {
    public function __construct() {
        remove_action('comment_form', 'wp_comment_form_unfiltered_html_nonce');
        add_action('comment_form', array($this, 'wp_comment_form_unfiltered_html_nonce'));
        add_filter('comment_reply_link', array($this, 'comment_reply_link'), 10, 4);
    }
 
    // wp-includes/comment-template.php: wp_comment_form_unfiltered_html_nonce
    public function wp_comment_form_unfiltered_html_nonce() {
        $post = get_post();
        $post_id = $post ? $post->ID : 0;
 
        if ( current_user_can( 'unfiltered_html' ) ) {
            wp_nonce_field( 'unfiltered-html-comment_' . $post_id, '_wp_unfiltered_html_comment_disabled', false );
            //echo "<script>(function(){if(window===window.parent){document.getElementById('_wp_unfiltered_html_comment_disabled').name='_wp_unfiltered_html_comment';}})();</script>\n";
        }
    }
 
    // wp-includes/comment-template.php: get_comment_reply_link
    public function comment_reply_link($link, $args, $comment, $post) {
        $data_tag = 'data-reply="'.$args['add_below'].','.$comment->comment_ID.','.$args['respond_id'].','.$post->ID.'" ';
 
        return preg_replace('/onclick=\'.*\'/', '', str_replace('<a ', '<a '.$data_tag, $link));
    }
}
 
new wp_csp;
 
?>

JavaScirpt

jQuery(function($){
    // wp_comment_form_unfiltered_html_nonce
    if (document.getElementById("commentform") != null) {
        if (window===window.parent && document.getElementById('_wp_unfiltered_html_comment_disabled') != null) {
            document.getElementById('_wp_unfiltered_html_comment_disabled').name = '_wp_unfiltered_html_comment';
        };
    };
 
    // comment_reply_link
    if (document.getElementById("comments") != null) {
        $("a.comment-reply-link").click(function(){
            var data = $(this).data("reply").split(",");
            if (data.length != 0) {
                return addComment.moveForm(data[0]+"-"+data[1], data[1], data[2], data[3]);
            };
 
            return true;
        });
    };
});

 

wp_comment_form_unfiltered_html_nonce

これはコメントフォームに隠しフィールドとインラインJavaScriptを生成しているアクション。
remove_actionでcomment_formから削除して、add_actionでwp-includes/comment-template.phpからコピーしてきたコードを実行させます。

何でコピーしてきたか・・と言うと、JavaScriptがechoで直接出力されていて、バッファリングとかを使わないと出力コントロールできないから。
それにバッファリングはコストがかかるし、それならコピーしてきた方がスマートかな・・・と。

ちなみに、出力されるJavaScriptを整形するとこうなります。

(function(){
    if(window===window.parent){
        document.getElementById('_wp_unfiltered_html_comment_disabled').name='_wp_unfiltered_html_comment';
    }
})();

スパム対策とかなんかそんな感じがするけど、inputのname属性って変更できないんじゃなかったっけ。
たしかキャプチャを作ったとき試したけど変更できなかったんだよなあ。
 

comment_reply_link

これはコメントの「返信」エレメントを生成する所で、onclickのインラインJavaScriptを生成しています。
修正はadd_filterでcomment_reply_linkをフィルターすれば変更可能なので楽ちんですねー

修正方針はonclickの代わりにカスタムデータ属性に必要なデータを格納しておいて後からjQueryでイベントを設定します。
 
 
これでscript-src 'self'な状態でWordPressを使用できます。ヽ(´ー`)ノ

ちなみに、WordPressの管理画面はインラインスクリプトやevalを使いまくりなので'unsafe-inline' 'unsafe-eval'が必須ですw
なので管理画面のある/wp-admin/だけは別のCSPを設定する必要があります。

またfontがdataスキームで指定されているのでfont-src 'self' data:;が必要になります。
その他、AkismetやWordPressのAPIを許可すると、うちの場合はこんな感じになりましたー

<Directory ..()../wp-admin>
    Header always set Content-Security-Policy "default-src 'self' *.wolfs.jp; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.wolfs.jp; child-src 'self' data: www.youtube.com akismet.com; style-src 'self' 'unsafe-inline' *.wolfs.jp; font-src 'self' data: *.wolfs.jp; img-src 'self' data: *.wolfs.jp *.gravatar.com *.wordpress.com *.wp.com;"
</Directory>

 
管理画面でスクリプトのインラインとeval許可はダメな気がするけれど、不要な外部参照は防ぐことができるのでまあいいか・・

と言うことで、ユーザー側の方はインラインスクリプトを無くすことができたので取りあえずは良しとします。

関連するかもしれない記事



トラックバックURL


スパム対策のためトラックバックURLを動的に生成しています。
生成されるトラックバックURLはコンテンツURLと紐付けされますので、コンテンツURLで指定したサイト以外では使用できませんのでご注意ください。
トラックバックの注意事項などの詳しい説明は About ページを閲覧してください。
「コンテンツURL」を入力し「URL生成」ボタンを押してください。
トラックバックを送る際はあなたの記事やコンテンツにこの記事のURLを書くかリンクしておいてください。
URLがない場合はスパムとして削除され以降の全トラックバックは拒否されます。

コメントを残す

メールアドレスは公開されません、また は必須項目です。
このブログに初めてコメントする方は こちら をご覧ください。


画像認証は待機中です、先にコメント本文を入力して下さい。

コメントを送信しています、しばらくお待ち下さい...
(Akismetスパムデータベース及びブラックリストへの照会を行っています)


キャンセルをした場合でもコメント投稿が完了している場合があります