CSP(Content-Security-Policy)とは
ブラウザで動作する外部リソースやインラインソースに対するXSSやコンテンツインジェクションに対するセキュリティの仕組み。
許可された範囲外の外部リソースやインラインソースの利用をブラウザレベルで拒否する。
HTML配信時のレスポンスヘッダーに、Content-Security-Policy
を配信することで動作する。
Content-Security-Policy
では、ディレクティブと呼ばれるリソースの利用を制限する命令によって実現する。
ディレクティブは、階層構造を持っており、上位は下位が存在しないときに採用される。
以降は、https://w3c.github.io/webappsec-csp を参照した結果を書いていく。
用語
ディレクティブ
命令
インラインソース
HTML上に記述されるソース。
JavaScriptの例
<script>
console.log('aaaa');
</script>
StyleSheetの例
<div style='font-weight: bold;'>aaa</div>
ディレクティブのフォールバック関係
下から宣言有無を確認し、無い場合、上流へ参照していく仕組みとその関係。
両方ある場合は下流が優先する。継承のような考え方はない。
部分的に適用も可能。
例えば、script-src
のみを制限して、img-src
, style-src
はインラインソース、外部ソースの範囲に対して制限を設けない等。
このため、default-src
の指定を行うと、実質すべてのディレクティブの宣言になるため、影響が大きい。
ディレクティブの記述方法
ヘッダー名をContent-Security-Policy
とし、
値を以下のように記載する。
Content-Security-Policy: [<ディレクティブ> [<ソース>]]
※[]は繰り返し可能な範囲を指す。
各ディレクティブの機能
Fetch Directives
ディレクティブ名 | 概要 | value |
---|---|---|
child-src | Documentに記載されたiframe やscript タグ内に記載していた外部Workerの読み込みを指定範囲のみに制限する。 |
serialized-source-list |
connect-src |
fetch ,XHR ,EventSource ,sendBeacon ,aタグのping ,WebSocket を対象に接続先を制限。 |
serialized-source-list |
default-src | すべての未定義時のフォールバック先 | serialized-source-list |
font-src | 外部フォントの読み込みを制限。 | serialized-source-list |
frame-src | Nestされたコンテンツ(iframe )を読み込む際の制限。 |
serialized-source-list |
img-src | imgの読み込み制限。 | serialized-source-list |
manifest-src | WebApplicationManifest(<link> )の読み込み制限。 |
serialized-source-list |
media-src |
<audio> ,<video> の読み込み制限。 |
serialized-source-list |
object-src | プラグインコンテンツ(object ,embed )の読み込み制限。 |
serialized-source-list |
prefetch-src |
<link rel="prefetch" .../> による先読み制限。 |
serialized-source-list |
script-src-elem |
script タグのsrc やインラインスクリプトを対象。・ onclick のようなイベントハンドラーは対象外・ unsafe-eval を使えない。・ worker-src のフォールバックにならない。 |
serialized-source-list |
script-src-attr | イベントハンドラー(onlick ,onchange ...)にのみ有効なscript-src 。 |
serialized-source-list |
style-src-elem | インラインスタイル以外を対象 | serialized-source-list |
style-src-attr | インラインスタイルのみ対象 | serialized-source-list |
worker-src | WebWorkerのソースを対象。 | serialized-source-list |
Other Directives
ディレクティブ名 | 概要 | value |
---|---|---|
webrtc | WebRTCを介して接続を確立できるかどうかを制限します。 | 'allow' 'block' |
Document Directives
ディレクティブ名 | 概要 | value |
---|---|---|
base-uri | ドキュメントのBase-URIを制御する。 | serialized-source-list |
sandbox |
sandbox 属性を持つiframe タグにおいて、許容するsandbox 属性のトークンを指定する。 |
"" / token *( required-ascii-whitespace token ) |
Navigation Directives
ディレクティブ名 | 概要 | value |
---|---|---|
form-action | フォーム送信のターゲットとして許容できるものを指定する。 | serialized-source-list |
frame-ancestors |
<frame> ,<embed> ,<object> ,<iframe> などのフレーム表示タグにおける表示許容ソースを指定する。 |
ancestor-source-list=ancestor-source *(ancestor-source) ancestor-source= scheme-source / host-source / "'self'" |
navigate-to | Navigate先の制限。 | serialized-source-list |
Reporting Directives
ディレクティブ名 | 概要 | value |
---|---|---|
report-uri | CSP違反時の通知先 | uri-reference *( required-ascii-whitespace uri-reference ) |
report-to | レポートグループを指す。 | token |
sourceとは
許容するリソースを指す。
指定方法は以下のとおり。
source | 概要 | 記述方法 |
---|---|---|
self | 配信元オリジンからのものを許可。 | 'self' |
none | 一切許可しない | 'none' |
unsafe-inline | インラインソースを許容する。<script> console.(123) </script>
|
'unsafe-inline' |
unsafe-eval | 文字列をソースとしたスクリプト実行を許容する。いわゆるevalを許容する。 | 'unsafe-eval' |
strict-dynamic | nonceと併用。許容したソースから派生して追加読み込みされたソースを許容する。インラインソースも許容。 | 'strict-dynamic' |
unsafe-hashes |
unsafe-inline よりセキュリティレベルの高い施策。外部化できないようなイベントハンドラーのハッシュを列挙することで対応する。hash-source と併用する。 |
'unsafe-hashes' |
report-sample | 違反しているコードのサンプルをレポートに含める。 | 'report-sample' |
unsafe-allow-redirects |
navigate-to でリストされたリダイレクト先以外も許容する? |
'unsafe-allow-redirects' |
wasm-unsafe-eval | WASM関連の以下を許可する。 new WebAssembly.Module() WebAssembly.compile() WebAssembly.compileStreaming() WebAssembly.instantiate() WebAssembly.instantiateStreaming() |
'wasm-unsafe-eval' |
host-source | オリジン一致、ファイル名まで一致 |
https://example.com https://example.con/js/static.js
|
scheme-source | URIスキームレベルで許容する。 |
http: https:
|
domain | ドメイン、サブドメイン |
example.com *.example.com
|
nonce-source | ページリクエスト単位で発行したノンス、ナンス値。not only once値。インラインソースも許容。 | nonce-{base64エンコードされたランダムな値} |
hash-source | ハッシュ値。SHA-256,384,512。 |
sha256-{ソースを左記計算法で計算したハッシュ値} sha384-{ソースを左記計算法で計算したハッシュ値} sha512-{ソースを左記計算法で計算したハッシュ値}
|
実際
注目株や分かりづらいものを動作確認していく。
-
'strict-dynamic'
ソース -
navigate-to
ディレクティブ -
'unsafe-allow-redirects'
ソース -
'script-src-elem'
ディレクティブ -
'script-src-attr'
ディレクティブ
環境
name | version | |
---|---|---|
OS | Ubuntu | 20.04.3 LTS |
Server | express(NodeJS) | 4.16.1 |
Template engine | pug(NodeJS) | 3.0.2 |
'strict-dynamic'
ソース
検証内容
- インラインスクリプトAをnonceで許容
- Aにて、動的にインラインスクリプトBをHTMLへ追記
HTML
<!DOCTYPE html>
<html>
<head>
<title>csp-test-page</title>
<link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body><h1>csp-test-page</h1>
<p>Welcome to csp-test-page</p>
<p>Server Date Tue May 03 2022 23:48:15 GMT+0900 (日本標準時)</p>
<script nonce="297f55f3-1211-4957-9d11-c4e54cf42bb6">console.log("inline script readed")
const externalScript = document.createElement("script")
externalScript.innerHTML = "console.log('inner script')"
document.body.append(externalScript)</script>
<br><a href="./">index page</a></body>
</html>
初期表示及びレスポンスヘッダー
初期表示後のコンソール
補足
スクリプトBを外部リソース化して、src属性で参照する形でも同様。
navigate-to
ディレクティブ
まだ、どこのブラウザも未サポート(2022/5/4時点)。
参照:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/navigate-to
HTML
TODO
初期表示及びレスポンスヘッダー
TODO
初期表示後のコンソール
TODO
'unsafe-allow-redirects'
ソース
navigate-to
がサポートされてから検証。
HTML
TODO
初期表示及びレスポンスヘッダー
TODO
初期表示後のコンソール
TODO
'script-src-elem'
ディレクティブ
script-src-elem.html
└ インラインスクリプト(nonceで許容)
インラインスクリプトにconsole.log("inline script readed")
を仕込む。
以下をレスポンスヘッダにしたページを配信する。
Content-Security-Policy: script-src-elem 'strict-dynamic' 'nonce-${nonce}';
※${nonce}は、実際にはランダムな文字列。今回はUUID。
HTML(実際にサーバサイドレンダリングされたもの)
<!DOCTYPE html>
<html>
<head>
<title>csp-test-page</title>
<link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body><h1>csp-test-page</h1>
<p>Welcome to csp-test-page</p>
<p>Server Date Tue May 03 2022 23:22:32 GMT+0900 (日本標準時)</p>
<p>nonce is 551f9bb7-c9b8-41b9-851b-43d065785fcd</p>
<script nonce="551f9bb7-c9b8-41b9-851b-43d065785fcd">console.log("inline script readed")</script>
<br><a href="./">index page</a></body>
</html>
初期表示およびレスポンスヘッダー
初期表示後のコンソール
'script-src-attr'
ディレクティブ
イベントハンドラーにconsole.log('clicked.');
を仕込む。
csp-script-src-attr.html
└ buttonタグ
・onclickにインラインスクリプト
buttonタグには、nonce
属性は無いため、nonce-source
による許容は不可能。
script-src-attr
では、インラインスクリプトはunsafe-hashes
もしくはunsafe-inline
が必要。
unsafe-inline
については割愛する。
unsafe-hashes
は、hash-source
と併用する。
以下をレスポンスヘッダにしたページを配信する。
Content-Security-Policy: script-src-attr 'unsafe-hashes' 'sha256-Jhvs7RdTid8e4XmWHITVrVl1z2kekJ8UP2C22dB7VxI=';
HTML
<!DOCTYPE html>
<html>
<head>
<title>csp-test-page</title>
<link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body><h1>csp-test-page</h1>
<p>Welcome to csp-test-page</p>
<p>Server Date Tue May 03 2022 22:50:31 GMT+0900 (日本標準時)</p>
<button onclick="console.log('clicked.');">script-src-attr-test</button>
</body>
</html>
初期表示およびレスポンスヘッダー
ボタン押下後のコンソール
導入について
SSR(Server Side Rendering)アプリケーションとCSR(Client Side Rendering)アプリケーションにおいて評価してみる。
SSR
導入の効果はある程度あり、制御も比較的容易と考えられる。
常に必要かと言えば、サイトの公開範囲(企業内に閉じたサイトなら不要、とか)やサービスの性質による(金銭等を直接扱うようなサイトであれば必須、など)。
CSR(SPA)
ハードルが高い
まず、ハードルがある。
基本的にbundleされたJSを読み込むことになるため、strict-dynamic
ソースを活用する方向性になると考える。しかし、ホスティングをベースとして提供されることが基本であるため、nonce-source
をCSPとして生成するタイミングと動的にhtmlへ設定するタイミングがない。
BFF層として、index.htmlを返すようなサーバサイド処理を設ければ可能ではある。
hash-source
も同様の問題を抱えている。
ハードルはあるのだが、CSRというかSPAはインタラクティブな処理、CDNの利用や外部APIの利用も多い。
CSPで適切なセキュリティポリシーを敷くことで防げることは多いのではないだろうか。
BFF導入にはハードルがあると思うが、認証時のトークン等についても保管場所として活用することでBFFにセキュリティ要件を満たすための役割を担わせることである程度の効率化も期待できる。
参考ページ
https://web.dev/csp/
https://auth0.com/blog/deploying-csp-in-spa/