意外と奥深いContent Security Policy（CSP）

# CSPとは
コンテンツセキュリティポリシーは簡単に説明するとクロスサイトスクリプティング (XSS) やデータインジェクション攻撃などのような、特定の種類の攻撃を緩和するためのセキュリティレイヤーでHTTPレスポンスヘッダーに指定して利用します。

# CSPの目標
2021年6月29日W3Cワーキングドラフトによって以下のように定義されています。

1. 開発者が細かく制御することでコンテンツインジェクション攻撃のリスクを軽減する
    - 特定の[Document](https://developer.mozilla.org/ja/docs/Web/API/Document)または[Worker](https://developer.mozilla.org/ja/docs/Web/API/Worker)に代わって要求（およびその後埋め込みまたは実行）できるリソース
    - インラインスクリプトの実行
    - [eval()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/eval)および同様の構造を介した動的コード実行
    - インラインスタイルの適用 
1. 特定のリソースを埋め込むことができるオリジンを開発者が細かに制御できるようにし、コンテキストにリソースを埋め込む必要がある悪意のある攻撃のリスクを軽減する
1. 開発者がアプリケーションの特権を減らすことを可能にするポリシーフレームワークを提供する
1. 開発者が実際に悪用されている欠陥を検出できるようにするレポートメカニズムを提供する

# CSPの設定方法
HTTPレスポンスヘッダーに以下のように設定すれば利用できます。

```
Content-Security-Policy: policy
```

上記の***policy***には本設定を行うサイトが適用したいCSPを表すディレクティブから構成される文字列を指定します。

他にもExpressでは直接HTTPレスポンスヘッダーを指定するのではなく、express-helmetというパッケージを利用する方法があります。
[helmetjs/helmet: Help secure Express apps with ... - GitHub](https://github.com/helmetjs/helmet)

# CSPのディレクティブ
> ディレクティブとはコンピュータプログラムのソースコードに記述される要素の一つで、そのコードを解釈・変換するソフトウェア（コンパイラやプリプロセッサなど）への指示や指定などを与えるためのもの。

要するにCSPにおいてはCSPの内容を指定するための要素の一つということです。

例えば、具体的なディレクティブの指定方法は下記のように記述します。

- HTTPヘッダー

```
Content-Security-Policy：[ディレクティブ名]　[ソース][ソース]; [ディレクティブ] [ソース]; ...

Content-Security-Policy:img-src 'self' https://example.com/
```

- metaタグ

```
<meta http-equiv="Content-Security-Policy" content="[ディレクティブ] [ソース] [ソース]; [ディレクティブ] [ソース] [ソース]; ...">

<meta http-equiv="Content-Security-Policy" content="img-src 'self' https://example.com/">
```

上記の指定方法では設定されたサイト自身のオリジンと*https\://example.com/*からの画像やfaviconのみがコンテンツとして有効となり、それ以外のオリジンからはコンテンツとして受け付けないようになります。

# デフォルトポリシー
デフォルトではポリシーにより以下の項目が制限されます。

- *data: URL*でのコンテンツの埋め込み
- *\<style>*要素でのCSSの設定
- インラインのstyle属性
- *\<script>*要素内のインラインスクリプト
- onclickなどのインラインのイベント属性
    - *\<div onClick="console.log('click')">Click\</div>*
- *\<a href=’javascript:’>*のようなhtmlへのjavascriptの埋め込み
- *eval()*や*Function()*
    - *eval("console.log('output');");*
    - *const output = new Function("console.log('output');");*
- *setInterval()*や*setTimeout()*内の文字列リテラルを渡したメソッド
    - *window.setTimeout("alert(\"Hello World!\")", 500)*

# フェッチディレクティブ
フェッチディレクティブは、特定のリソース種別がロードされうる場所を制御します。

- connect-src
    - scriptインターフェースによってロードされるURLを制限します。
- default-src
    - 別のディレクティブに対する代替として提供します。例えば、指定されていないディレクティブには本ディレクティブで設定されたポリシーが適用されます。
- font-src
    - [@font-face](https://developer.mozilla.org/ja/docs/Web/CSS/@font-face)によってロードされるフォントに対する有効なソースを指定します。
- frame-src
    - *\<frame>*や*\<iframe>*のような要素によってロードされる入れ子状のコンテンツの閲覧に対する有効なソースを指定します。
- img-src
    - 画像やfaviconに対する有効なソースを定義します。
- manifest-src
    - アプリケーションのマニフェストファイルに対する有効なソースを指定します。
- media-src
    - *\<audio>*や*\<video>*、*\<track>*要素によってロードするメディアに対する有効なソースを指定します。
- object-src
    - *\<object>*や*\<embed>*や*\<applet>*要素に対する有効なソースを指定します。
- prefetch-src
    - 事前にフェッチされるか描画される有効なソースを指定します。
- script-src
    - JavaScriptに対する有効なソースを指定します。
- script-src-elem
    - JavaScriptの*\<script>*要素に対する有効なソースを指定します。
- script-src-attr
    - JavaScript のインラインイベントハンドラーに対する有効なソースを指定します。
- style-src
    - スタイルシートに対する有効なソースを指定します。
- style-src-elem
    - スタイルシートの*\<style>*および*\<link>*要素に*rel="stylesheet"*がついたもののに対する有効なソースを指定します。
- style-src-attr
    - 個々のDOM要素に適用されるインラインスタイルの有効なソースを指定します。
- worker-src
    - Worker、SharedWorker、ServiceWorkerスクリプトに対する有効なソースを指定します。

その他、ディレクティブの詳しい内容については[引用元](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Security-Policy)をご確認ください。

# ソースの指定方法
|名称　|事例　|
|:-------------|:--------------|
|スキーム|data: https: http: blob:|
|ホスト名|example.com|
|URI|https\://example.com:443|
|ワイルドカード|\*://\*.example.com:*<br/>スキーム、ホスト名、ポートの左端のみ指定可能|

# ソースに指定可能なキーワード
|キーワード　|事例　|
|:-------------|:--------------|
|'self'|現在のオリジン|
|'unsafe-inline'|インラインJavaScript及びCSSを許可|
|'unsafe-eval'|*eval()*や*function()*などの関数を許可|
|'none'|全てを無効化(一般的には使用しない)|

デフォルトで制御されている通り、*unsafe-inline*及び*unsafe-eval*はモダンブラウザでは非推奨とされています。その代わりにnonce、もしくはhashを用います。

# nonce
nonceはインラインスクリプトとインラインのイベント属性、インラインのstyle属性に対して、ホワイトリスト形式でCSPのポリシー上で許可する仕組みです。

まず、許可したいインラインスクリプトのnonce属性に、BASE64エンコードしたテキストを埋め込みます。
その後にCSPのscript-src、もしくはstyle-srcに対して*'nonce-BASE64_value'*の形式で値を設定します。

```html
<script nonce="YWJjZGVmZw==">
  // 何らかの処理
</script>
```

```
Content-Security-Policy: script-src 'nonce-YWJjZGVmZw==';
```

nonceはランダムな値で構わないので、randomBytes()などで生成したランダム文字列をBASE64に変換した値を利用すれば良いです。

# hash
目的は先ほどのnonceと同様です。

まず、scriptタグもしくはstyleタグを除いた本体のハッシュ値を求めます。
(CSPではsha256及びsha384、sha512に対応)

その後にCSPのscript-src、もしくはstyle-srcに対して*'ハッシュアルゴリズム名-hash'*の形式で値を設定します。

```
Content-Security-Policy: script-src 'sha256-B2yPHKaXnvFWtRChIbabYmUBFZdVfKKXHbWtWidDVF8=';
```


# 指定したポリシーのテスト
[report-onlyモード](https://www.w3.org/TR/CSP2/#content_security_policy_report_only)で動作させることで指定したポリシーを検証することができます。このモードではポリシーによるコンテンツブロックは行われず、何らか違反があった場合は指定したURIへ報告される。

```
Content-Security-Policy-Report-Only:　[ディレクトリ名] [ソース]; report-uri [uri]; report-to [json-field-value];

Content-Security-Policy-Report-Only: default-src https:;
report-uri https://example.com：４４３/csp-reports;
report-to { "group": "csp-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://example.com/csp-reports"}]},{"group": ...};
```

report-toが設定されている場合、report-uriは無視されるが、下位互換性を確保するために指定することが推奨されている。
ちなみにreport-onlyモードはmeta要素内ではサポートされていない。

# 最後に
expressのセキュリティを検討する際に調べていたところ、問題にぶち当たった箇所でしたので、出来るだけしっかりと調べてみました。意外と奥深い...

# 参考サイト
[Content Security Policy Level 3 W3C Working Draft, 29 June 2021](https://www.w3.org/TR/CSP/)
[Content Security Policy フェッチディレクティブの一覧 - MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Security-Policy)
[CSP(コンテンツセキュリティポリシー)について調べてみた - techblog.securesky-tech.com](https://techblog.securesky-tech.com/entry/2020/05/21/)