Observatory by Mozillaでセキュリティチェック

Observatory by Mozillaでセキュリティチェック 技術ネタ

さらにサイトのセキュリティを高めるため、Observatory by Mozilla でチェックしてみました。



前提

いつもの、本サイトの簡単な構成です。

  • KUSANAGI for さくらのVPS
  • Nginx
  • PHP 7

改善前の評価

改善前の評価は F で スコアは 0/100 でした。非常に残念な結果ですね・・・
以下のものがマイナス評価となっていました。

TestScoreExplanation
Content Security Policy-25Content Security Policy (CSP) header not implemented
Subresource Integrity-50Subresource Integrity (SRI) not implemented, and external scripts are loaded over HTTP or use protocol-relative URLs via src=”//…”
X-Content-Type-Options-5X-Content-Type-Options header not implemented
X-Frame-Options-20X-Frame-Options (XFO) header not implemented
X-XSS-Protection-10X-XSS-Protection header not implemented

改善内容

マイナス評価となっている5項目について、以下のような改善を行いました。

Content Security Policy

このあたりを読むとわかりますが、簡単に言うと、XSSなどの攻撃を軽減するためのセキュリティレイヤーのようです。
対応については、結構大変でした。。。指摘がなくならず悪戦苦闘しながら色々試して、やっと解決できました。対応のポイントを書きたいと思います。

追加したヘッダ

最終的には以下のHTTPヘッダーを追加しました。

Content-Security-Policy: default-src https: 'self'; base-uri 'none'; script-src 'self' 'nonce-{ユニークな値}' ajax.googleapis.com www.googletagmanager.com; object-src 'none'; style-src 'self' 'unsafe-inline' fonts.googleapis.com; img-src 'self' www.google-analytics.com; media-src 'self'; frame-src 'self'; font-src 'self' fonts.gstatic.com;

1つ1つの項目について詳細は書きませんが、簡単にヘッダーを生成してくれるページがあるので、ここで作成すると少し楽になります。本サイトもベースはここで作成しました。

base-uri ‘none’ について
Observatory by Mozilla では指摘されていませんが、CSP Evaluator で指摘されていたため設定しています。

nonce-source(インラインのスクリプト)

上に書いたヘッダ「script-src ‘nonce-{ユニークな値}’」ですが、実は、この nonce-source の指定する仕様が存在していることに気づかなかったため、指摘がなくならず苦労しました。

これを見つけることができたのは、実際のHTMLから怪しいと思われる部分を削除していくと、HTML内に書かれたインラインのJavaScriptコードが指摘されているとわかり、それに対して、 script-src を色々試しました。 ‘unsafe-inline’ でもダメだし、挫折しかけていたところ、nonce-source というものがあることを見つけました。

詳細はこのあたりとかに書いてありますが、どうやら、インラインスクリプトが改竄(外部からのスクリプト挿入など)されていないかnonce値でチェックするもののようです。

現時点の本サイトのインラインスクリプトは Google Analytics のみでしたので、これについて、HTTPヘッダーの script-src に ‘nonce-{ユニークな値}’ を追加し、スクリプト部分を以下のように nonceプロパティを追加しました。

<script nonce="{ユニークな値}">
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', '{発行されたID}');
</script>

これについて、具体的な対応としては、PHP側で行いことにしました。
本サイトは、WordPressのテーマ「スワロー」を使用しています。このテーマ前提の対応になりますが、functions.php に以下コードを追加しました。

$CSP_NONCE = base64_encode(random_bytes(16));

// Content-Security-Policyヘッダ追加
function add_custom_header( $headers ) {
    global $CSP_NONCE;
    if(!is_user_logged_in()){
        $headers['Content-Security-Policy'] = "default-src https: 'self'; base-uri 'none'; script-src 'self' 'nonce-{$CSP_NONCE}' ajax.googleapis.com www.googletagmanager.com; object-src 'none'; style-src 'self' 'unsafe-inline' fonts.googleapis.com; img-src 'self' www.google-analytics.com; media-src 'self'; frame-src 'self'; font-src 'self' fonts.gstatic.com";
        return $headers;
    }
}
add_filter( 'wp_headers', 'add_custom_header' );

function after_func() {
    // 親テーマで出力しているGoogle Analytics タグを消して、CSP-nonce 対応用のものを追加する
    if ( get_option( 'other_options_ga' ) ) {
        function meta_analytics_custom() {
            global $CSP_NONCE;
            $analyticstag = get_option( 'other_options_ga' );
            $headanalytics = <<<EOM
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id={$analyticstag}"></script>
<script nonce="{$CSP_NONCE}">
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', '{$analyticstag}');
</script>
EOM;
            echo str_replace("\n", "", $headanalytics);
        }
        remove_action('wp_head', 'meta_analytics', 999);
        add_action('wp_head', 'meta_analytics_custom', 999);
    }
}
add_action('after_setup_theme', 'after_func');

Subresource Integrity

これについては、 こことかを読むとわかりますが、簡単に言うと、外部リソースが改ざんされていないかチェックするためのものです。
本サイトでは、現時点で外部リソースを読み込んでいる箇所が2つありましたので、その2箇所について対応を行いました。

jQuery

Google の CDN から jQuery を読み込んでいますので、これについて対応をしました。
まず、このファイルのハッシュ値を求めます。やり方は色々あるようですが、今回は以下のコマンドを使って行いました。

curl 'https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js' | openssl dgst -sha384 -binary | openssl enc -base64 -A

この値を元に、scriptタグに、「integrity」「crossorigin」プロパティを追加しました。

<script type='text/javascript' defer integrity='sha384-o6l2EXLcx4A+q7ls2O2OP2Lb2W7iBgOsYvuuRI6G+Efbjbk6J4xbirJpHZZoHbfs' crossorigin='anonymous' src='https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js'></script>
プロトコル指定に注意
まず最初に上に書いた「integrity」「crossorigin」を追加したのですが、再チェックしても指摘がなくならず、しばらく悩んでいました。どうやら、外部ソースの指定を “//ajax.googleapis.com” と書いてはダメだということがわかり、”https://ajax.googleapis.com” と書くことで指摘がなくなりました。ご注意ください。

Google Analytics

次に、Google Analyticsも外部から読み込んでいますので、これについて対応しようとしましたが、このjsはGoogle側で勝手に修正されるので、事前にハッシュ値を計算して埋めることができないようです。よって、ここの対応については諦めました。

参考までに、この Google Analytics タグを削除すると、評価は A+ になりました。

X-Content-Type-Options

こことかがわかり易いと思います。簡単にいうと、HTTPヘッダの Content-Type に従うようにし、ファイルの内容から判断しないようにするものらしいです。
本サイトでは、Nginx の conf に以下のコードを追加しました。

add_header X-Content-Type-Options nosniff;

X-Frame-Options

これも上と同じページに書かれていますが、外部サイトの iFrame に自サイトが指定されても読み込まれないようにするものらしいです。
Nginx の conf に以下のコードを追加しました。

add_header X-Frame-Options SAMEORIGIN;

X-XSS-Protection

このあたいを読んでもらうとわかりますが、XSS対策機能を有効にするものらしいです。
こちらについても、Nginx の conf に以下を追加しました。

add_header X-XSS-Protection "1; mode=block";

改善後の評価

改善後の評価はみごと A で スコアは 95/100 になりました。なお、-5 については、以下の指摘が残っているためで、上でも書きました、Google Analytics の SRI は対応できないため、これで良しとしました。

TestScoreExplanation
Subresource Integrity-50Subresource Integrity (SRI) not implemented, but all external scripts are loaded over HTTPS

改善を終えて

サイトのセキュリティが向上することは当然ですが、自分の知識を深めることにもつながるので、今後も情報はウォッチしていきたいと考えています。