-ao- ramune blog

©2024 unio / GO2直営からふるラムネ
2019年02月20日

Vue vs. Content Security Policy

厳し目のCSPを適用したい

投稿系のアプリを作る際に、なるべくXSSの脅威を減らすため CSP を厳し目に設定したいケースがあります。

CSPでunsafe-evalを設定しない場合、Vueの 完全パッケージ ではエラーが出てしまい動かすことができません。

VueのCSPエラー
                
                    [Vue warn]: It seems you are using the standalone build of Vue.js in an environment
                    with Content Security Policy that prohibits unsafe-eval.
                    The template compiler cannot work in this environment.
                    Consider relaxing the policy to allow unsafe-eval or pre-compiling your templates into render functions.

                    ## ざっくり日本語訳
                    [Vue warn]: スタンドアロンビルドのVue.jsを、evalが禁止された環境上で利用しています。
                    この環境ではテンプレートコンパイラは動きません。
                    evalの利用を許可するか、テンプレートをrender関数でプリコンパイルすることを検討してください。
                
            

今回はCSPのscript-srcで一番厳しいscript-src 'self';を目標に、どうやってVueを動かすかの解説を行います。 なおビルド環境はwebpackを利用しますが、基本的な考えは他のビルドツールやCDN配布のVueスクリプト利用でも変わりません。

CSP vs. Vue

下記はテスト用にCSPを設定したページとVueが書かれたjsファイルです。

CSPを設定したHTML
                
                    <!DOCTYPE html>
                    <html lang="ja">
                        <head>
                            <title>CSPテスト</title>
                            <meta http-equiv="Content-Security-Policy" content="script-src 'self';">
                        </head>
                        <body>
                            <main>
                                <span v-text="message"></span>
                            </main>
                            <script src="/build/test.js"></script>
                        </body>
                    </html>
                
            
test.js
                
                    import Vue from "vue";
                    new Vue({
                        el: "main",
                        data: {
                            message: "CSPテスト"
                        }
                    });
                
            

このままでは先程のevalエラーが出てしまうので、 テンプレートコンパイラの入っていないランタイムパッケージを利用します。

webpack.config.js
                
                    ...
                    resolve: {
                        ...
                        alias: {
                            ...
                            // ランタイムパッケージを利用
                            "vue$": "vue/dist/vue.runtime.esm.js"
                        }
                    },
                
            

Symfony4のWebpackEncoreBundleを利用している場合は、下記のように設定します。

Webpack Encore版のwebpack.config.js
                
                    const config = Encore.getWebpackConfig();
                    config.resolve.alias["vue$"] = "vue/dist/vue.runtime.esm.js";
                
            

ランタイムパッケージに切り替えるとevalエラーは消えますが、今度は別のエラーが出ます。

ブラウザのデベロッパーコンソールに表示されるエラー
                
                    vue.runtime.esm.js:619 [Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available.
                    Either pre-compile the templates into render functions, or use the compiler-included build.

                    ## ざっくり日本語訳
                    vue.runtime.esm.js:619 [Vue warn]: テンプレートコンパイラの入っていないランタイムビルドのVueを利用しています。
                    テンプレートをrender関数でプリコンパイルするか、コンパイラの入ったパッケージを利用してください。
                
            

もちろんテンプレートコンパイラは使えないので、render関数でプリコンパイルする方法で対応します。

render関数を使う

render関数を使うためVueをコンポーネント化します。 Vueコンポーネントはいくつかありますが、今回はシングルファイルコンポーネント(SFC)で記述します。

Test.vue
                
                    <template>
                        <main>
                            <span>{{ message }}</span>
                        </main>
                    </template>

                    <script>
                        export default {
                            props: {
                                message: String,
                            },
                        }
                    </script>
                
            

SFCを書いたら、Vueのエントリーjsもrender関数に書き直します。

test.js
                
                    import Vue from "vue";
                    // 作成したSFCをインポートする
                    import Test from "./Test";

                    new Vue({
                        el: "main",
                        render(createElement) {
                            // SFCをレンダリングする
                            return createElement(Test, {
                                props: {
                                    message: "CSPテスト",
                                }
                            });
                        },
                    });
                
            

最後にHTMLからv-textを外して完成です。

HTMLからバインド構文を除去
                
                    <!DOCTYPE html>
                    <html lang="ja">
                        <head>
                            <title>CSPテスト</title>
                            <meta http-equiv="Content-Security-Policy" content="script-src 'self';">
                        </head>
                        <body>
                            <main></main>
                            <script src="/build/test.js"></script>
                        </body>
                    </html>
                
            

これでCSPに違反せず、Vueが動くことを確認できると思います。

まとめ

今回はCSPのスクリプト設定で一番厳しいscript-src 'self';上でVueを動かしました。 ちなみにこの設定はインラインスクリプトも許可しないので、XSSの危険度をかなり下げることができます。 安全性が増す反面、外部サイトスクリプト(広告タグや計測タグ)を利用する場合は設定が面倒になるので、 リスク判断に応じて制限を緩めるのがオススメです。