コンテンツセキュリティポリシーでNode.jsアプリケーションを保護する方法
著者は、 Free Software Foundation を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
ブラウザがページをロードすると、コンテンツをレンダリングするために多くのコードが実行されます。 コードは、ルートドキュメントと同じオリジン、または異なるオリジンからのものである可能性があります。 デフォルトでは、ブラウザは2つを区別せず、ソースに関係なく、ページによって要求されたコードを実行します。 攻撃者はこのエクスプロイトを使用して、悪意を持ってスクリプトをページに挿入します。スクリプトは、ブラウザにコンテンツが有害であるかどうかを判断する方法がないため、実行されます。 これらの状況は、コンテンツセキュリティポリシー(CSP)が保護を提供できる場合です。
CSP は、クロスサイトスクリプティング(XSS)、クリックジャッキング、他の同様のエクスプロイト。 信頼できるコンテンツの「許可リスト」の作成を容易にし、許可リストに存在しないソースからのコードの実行をブロックします。 また、ポリシー違反を選択したURLに報告するため、潜在的なセキュリティ攻撃に遅れないようにすることができます。
CSPヘッダーを使用すると、ブラウザーがロードできるサイト上のコンテンツの承認済みソースを指定できます。 承認されたソースからのものではないコードは実行がブロックされるため、攻撃者がコンテンツを挿入してデータを吸い上げることは非常に困難になります。
このチュートリアルでは、 example Node.jsアプリケーションに実装することで、CSPヘッダーが提供するさまざまな保護を確認します。 また、CSP違反のJSONレポートを収集して、問題をキャッチし、エクスプロイトをすばやく修正します。
前提条件
このチュートリアルに従うには、次のものが必要です。
- マシンにインストールされているNode.jsの最新バージョン。 オペレーティングシステムに関連するNode.jsのインストール方法チュートリアルの手順に従って、Node.js開発環境をセットアップします。
この記事の執筆時点(2020年11月)では、 CSPレベル3ディレクティブを最もよくサポートしているため、最新のブラウザバージョン(できれば Chrome )も使用する必要があります。 また、CSP実装のテスト中は、サードパーティの拡張機能を無効にして、コンソールに表示される違反レポートに干渉しないようにしてください。
ステップ1—デモプロジェクトの設定
コンテンツセキュリティポリシーを作成するプロセスを示すために、このデモプロジェクトにコンテンツセキュリティポリシーを実装するプロセス全体を実行します。 これは、一般的なWebサイトまたはアプリケーションに近いさまざまなコンテンツを含む1ページのWebサイトです。 小さなVue.jsアプリケーション、 YouTube埋め込み、およびUnsplashから供給されたいくつかの画像が含まれています。 また、Googleフォントとコンテンツ配信ネットワーク(CDN)を介してロードされるブートストラップフレームワークを使用します。
このステップでは、テストサーバーまたはローカルマシンでデモプロジェクトをセットアップし、ブラウザーで表示します。
まず、次のコマンドを使用して、プロジェクトをファイルシステムに複製します。
- git clone https://github.com/do-community/csp-demo
プロジェクトディレクトリを設定したら、次のコマンドを使用してディレクトリに変更します。
- cd csp-demo
次に、次のコマンドでpackage.json
ファイルで指定された依存関係をインストールします。 express
パッケージを使用してWebサーバーをセットアップし、nodemon
は、ディレクトリ内のファイルの変更を検出したときにノードアプリケーションを自動的に再起動するのに役立ちます。
- npm install
依存関係をインストールしたら、次のコマンドを入力して、ポート5500
でWebサーバーを起動します。
- npm start
これで、ブラウザでyour_server_ip:5500
またはlocalhost:5500
にアクセスして、デモページを表示できます。 このページには、 Hello World!というテキスト、YouTubeの埋め込み、およびいくつかの画像があります。
次のセクションでは、最も基本的な保護のみを対象とするCSPポリシーを実装します。 その後、ページで許可する必要のあるすべての正当なリソースを明らかにするため、以降のセクションでそれを基に構築します。
ステップ2—基本的なCSPの実装
先に進んで、フォント、画像、スクリプト、スタイル、および埋め込みを現在のホストのみから発信されたものに制限するCSPポリシーを作成しましょう。 これを実現する応答ヘッダーは次のとおりです。
Content-Security-Policy: default-src 'self'; font-src 'self'; img-src 'self'; script-src 'self'; style-src 'self'; frame-src 'self';
このヘッダーのポリシーディレクティブの説明は次のとおりです。
font-src
は、フォントをロードできるソースを定義します。img-src
は、画像の読み込みが許可されるソースを定義します。script-src
は、Webページのスクリプト読み込み権限を制御します。style-src
は、スタイルシートソースを許可するためのディレクティブです。frame-src
は、フレーム埋め込みに許可されるソースを定義します。 (CSPレベル2 では非推奨でしたが、レベル3で復元されました。)default-src
は、特定のディレクティブがヘッダーで明示的に指定されていない場合のフォールバックポリシーを定義します。 これは、default-src
にフォールバックするディレクティブの完全なリストです。
この例では、指定されたすべてのディレクティブに、ソースリストで'self'
キーワードが割り当てられています。 これは、現在のホストからのリソース(URLスキームとポート番号を含む)のみの実行を許可する必要があることを示しています。 たとえば、script-src 'self'
は現在のホストからのスクリプトの実行を許可しますが、他のすべてのスクリプトソースをブロックします。
先に進んで、Node.jsプロジェクトにヘッダーを追加しましょう。
アプリを実行したまま、新しいターミナルウィンドウを開いて、server.js
ファイルを操作します。
- nano server.js
次に、Expressミドルウェアレイヤーに例のCSPヘッダーを追加します。 これにより、サーバーからのすべての応答にヘッダーが含まれるようになります。
const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
const app = express();
app.use(function (req, res, next) {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; font-src 'self'; img-src 'self'; script-src 'self'; style-src 'self'; frame-src 'self'"
);
next();
});
app.use(bodyParser.json());
app.use(express.static(path.join(__dirname)));
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname + '/index.html'));
});
const server = app.listen(process.env.PORT || 5500, () => {
const { port } = server.address();
console.log(`Server running on PORT ${port}`);
});
ファイルを保存し、ブラウザでプロジェクトをリロードします。 ページが完全に壊れていることに気付くでしょう。
CSPヘッダーは期待どおりに機能しており、ページに含めたすべての外部ソースは、定義されたポリシーに違反しているため、読み込みがブロックされています。 ただし、違反が発生するとWebサイトが破損する可能性があるため、これは新しいポリシーをテストするための理想的な方法ではありません。
これが、Content-Security-Policy-Report-Only
ヘッダーが存在する理由です。 Content-Security-Policy
の代わりに使用すると、発生した違反を報告しながら、ブラウザがポリシーを適用しないようにすることができます。つまり、サイトを危険にさらすことなくポリシーを調整できます。 ポリシーに満足したら、強制ヘッダーに戻って保護を有効にすることができます。
先に進み、server.js
ファイルのContent-Security-Policy
ヘッダーをContent-Security-Policy-Report-Only
に置き換えます。
- nano server.js
次の強調表示されたコードを追加します。
. . .
app.use(function (req, res, next) {
res.setHeader(
'Content-Security-Policy-Report-Only',
"default-src 'self'; font-src 'self'; img-src 'self'; script-src 'self'; style-src 'self'; frame-src 'self'"
);
next();
});
. . .
ファイルを保存し、ブラウザでページをリロードします。 ページは動作状態に戻りますが、ブラウザコンソールは引き続きCSP違反を報告します。 各違反には[Report Only]
のプレフィックスが付いており、ポリシーが適用されていないことを示します。
このセクションでは、CSPの初期実装を作成し、レポート専用モードに設定して、サイトを壊さずに改良できるようにしました。 次のセクションでは、最初のCSPによってトリガーされた違反を修正します。
ステップ3—ポリシー違反の修正
前のセクションで実装したポリシーは、すべてのリソースをオリジンのみに制限したため、いくつかの違反を引き起こしましたが、ページにはいくつかのサードパーティのアセットがあります。
CSP違反を修正する2つの方法は、ポリシー内のソースを承認するか、違反をトリガーするコードを削除することです。 正当なリソースがすべての違反を引き起こしているため、このセクションでは主に前者のオプションに集中します。
ブラウザコンソールを開きます。 CSPの現在の違反がすべて表示されます。 これらの問題をそれぞれ修正しましょう。
スタイルシートを許可する
コンソールの最初の2つの違反は、それぞれhttps://fonts.googleapis.com
とhttps://cdn.jsdelivr.net
からロードしているGoogleフォントとBootstrapスタイルシートによるものです。 style-src
ディレクティブを使用して、ページ上で両方を許可できます。
style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net;
これは、オリジンホストhttps://fonts.googleapis.com
およびhttps://cdn.jsdelivr.net
からのCSSファイルをページで実行する必要があることを指定します。 このポリシーは、(現在使用しているものだけでなく)許可リストドメインからの任意のスタイルシートを許可するため、非常に広範囲です。
代わりに、許可したい正確なファイルまたはディレクトリを使用して、より具体的にすることができます。
style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css;
これで、指定された正確なスタイルシートのみを実行できるようになります。 https://cdn.jsdelivr.net
からのものであっても、他のすべてのスタイルシートをブロックします。
更新されたstyle-src
ディレクティブを使用して、次のようにCSPヘッダーを更新できます。 ページをリロードするまでに、両方の違反が解決されます。
. . .
app.use(function (req, res, next) {
res.setHeader(
'Content-Security-Policy-Report-Only',
"default-src 'self'; font-src 'self'; img-src 'self'; script-src 'self'; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css; frame-src 'self';"
);
next();
});
. . .
画像ソースの許可
ページで使用している画像は、https://images.unsplash.com
という単一のソースからのものです。 次のようにimg-src
ディレクティブを介して許可しましょう。
. . .
app.use(function (req, res, next) {
res.setHeader(
'Content-Security-Policy-Report-Only',
"default-src 'self'; font-src 'self'; img-src 'self' https://images.unsplash.com; script-src 'self'; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css; frame-src 'self'"
);
next();
});
. . .
YouTubeの埋め込みを許可する
frame-src
ディレクティブを介して、<iframe>
などの要素を使用するネストされたブラウジングコンテキストの有効なソースを許可できます。 このディレクティブがない場合、ブラウザはchild-src
ディレクティブを探し、その後default-src
ディレクティブにフォールバックします。
現在のポリシーでは、フレームの埋め込みをオリジンホストに制限しています。 ポリシーを適用した後、CSPがYoutube埋め込みの読み込みをブロックしないように、https://www.youtube.com
を許可リストに追加しましょう。
frame-src 'self' https://www.youtube.com;
ここでは、www
サブドメインが重要であることに注意してください。 https://youtube.com
からの埋め込みがある場合、https://youtube.com
も許可リストに追加しない限り、このポリシーに従ってブロックされます。
frame-src 'self' https://www.youtube.com https://youtube.com;
更新されたCSPヘッダーは次のとおりです。 frame-src
ディレクティブを次のように変更します。
. . .
app.use(function (req, res, next) {
res.setHeader(
'Content-Security-Policy-Report-Only',
"default-src 'self'; font-src 'self'; img-src 'self' https://images.unsplash.com; script-src 'self'; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css; frame-src 'self' https://www.youtube.com https://youtube.com;"
);
next();
});
. . .
フォントファイルの許可
Googleフォントスタイルシートには、https://fonts.gstatic.com
のいくつかのフォントファイルへの参照が含まれています。 これらのファイルは現在、定義されているfont-src
ポリシー(現在は'self'
)に違反しているため、改訂されたポリシーでそれらを考慮する必要があります。
font-src 'self' https://fonts.gstatic.com;
CSPヘッダーのfont-src
ディレクティブを次のように更新します。
. . .
app.use(function (req, res, next) {
res.setHeader(
'Content-Security-Policy-Report-Only',
"default-src 'self'; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://images.unsplash.com; script-src 'self'; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css; frame-src 'self' https://www.youtube.com https://youtube.com;"
);
next();
});
. . .
ページをリロードすると、コンソールはGoogleフォントファイルの違反を報告しなくなります。
Vue.jsスクリプトを許可する
CDN にロードされたVue.jsスクリプトは、ページの上部に Hello world!テキストをレンダリングしています。 script-src
ディレクティブを介してページでの実行を許可します。 前述のように、CDNソースを許可する場合は具体的にすることが重要です。これにより、そのドメインでホストされている可能性のある他の悪意のあるスクリプトにサイトが開かれることはありません。
script-src 'self' https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js;
この例では、そのURLの正確なスクリプトのみをページで実行することを許可しています。 開発ビルドと本番ビルドを切り替える場合は、他のスクリプトURLも許可リストに追加する必要があります。または、/dist
の場所にあるすべてのリソースを許可して、両方のケースをカバーすることができます。一度:
script-src 'self' https://cdn.jsdelivr.net/npm/[email protected]/dist/;
関連する変更を加えた更新されたCSPヘッダーは次のとおりです。
. . .
app.use(function (req, res, next) {
res.setHeader(
'Content-Security-Policy-Report-Only',
"default-src 'self'; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://images.unsplash.com; script-src 'self' https://cdn.jsdelivr.net/npm/[email protected]/dist/; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css; frame-src 'self' https://www.youtube.com https://youtube.com;"
);
next();
});
. . .
この時点で、ページが依存するすべての外部ファイルとスクリプトを正常に許可しました。 ただし、ページにインラインスクリプトが存在するため、解決すべきCSP違反がもう1つあります。 次のセクションでは、この問題のいくつかの解決策について説明します。
ステップ4—インラインソースの処理
'unsafe-inline'
キーワードを使用してCSP内のインラインコード(<script>
タグのJavaScriptコードなど)を承認できますが、コードインジェクション攻撃のリスクが大幅に高まるため、お勧めしません。 。
このサンプルポリシーでは、ページ上の任意のインラインスクリプトの実行が許可されていますが、前述の理由により、これは安全ではありません。
script-src 'self' 'unsafe-inline' https://unpkg.com/[email protected]/dist/;
unsafe-inline
の使用を回避する最善の方法は、インラインコードを外部ファイルに移動し、その方法で参照することです。 これは、キャッシング、縮小、および保守性のためのより優れたアプローチであり、将来のCSPの変更も容易にします。
ただし、どうしてもインラインコードを使用する必要がある場合は、それらを許可リストに安全に追加するための2つの主要な方法があります。
オプション1—ハッシュを使用する
この方法では、スクリプト自体に基づいてSHAハッシュを計算し、それをscript-src
ディレクティブに追加する必要があります。 Chromeの最近のバージョンでは、ハッシュはコンソールのCSP違反エラーにすでに含まれているため、自分でハッシュを生成する必要はありません。
[Report Only] Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' https://unpkg.com/[email protected]/dist/". Either the 'unsafe-inline' keyword, a hash ('sha256-INJfZVfoUd61ITRFLf63g+S/NJAfswGDl15oK0iXgYM='), or a nonce ('nonce-...') is required to enable inline execution.
ここで強調表示されているセクションは、違反をトリガーした特定のインラインスクリプトを実行できるようにするために、script-src
ディレクティブに追加する必要がある正確なSHA256ハッシュです。
ハッシュをコピーして、次のようにCSPに追加します。
script-src 'self' https://unpkg.com/[email protected]/dist/ 'sha256-INJfZVfoUd61ITRFLf63g+S/NJAfswGDl15oK0iXgYM=';
このアプローチの欠点は、スクリプトの内容が変更されると、生成されるハッシュが異なり、違反が発生することです。
オプション2—ノンスを使用する
インラインコードの実行を許可する2番目の方法は、nonceを使用することです。 これらはランダムな文字列であり、内容に関係なくコードの完全なブロックを許可するために使用できます。
使用中のナンス値の例を次に示します。
script-src 'self' https://unpkg.com/[email protected]/dist/ 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'
CSPのナンスの値は、スクリプトのnonce
属性と一致する必要があります。
<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">
// Some inline code
</script>
ナンスは、攻撃者が悪意のあるスクリプトの実行に使用できないように、ページが読み込まれるたびに推測不能で動的に生成される必要があります。 このオプションを実装する場合は、crypto
パッケージを使用して、次のようにナンスを生成できます。
const crypto = require('crypto');
let nonce = crypto.randomBytes(16).toString('base64');
このチュートリアルでは、ユースケースの方が実用的であるため、ハッシュメソッドを選択します。
次のように、CSPヘッダーのscript-src
ディレクティブを更新して、唯一のインラインスクリプトのSHA256ハッシュを含めます。
. . .
app.use(function (req, res, next) {
res.setHeader(
'Content-Security-Policy-Report-Only',
"default-src 'self'; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://images.unsplash.com; script-src 'self' https://unpkg.com/[email protected]/dist/ 'sha256-INJfZVfoUd61ITRFLf63g+S/NJAfswGDl15oK0iXgYM='; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css; frame-src 'self' https://www.youtube.com https://youtube.com;"
);
next();
});
. . .
これにより、インラインスクリプトがコンソールからトリガーする最後のCSP違反エラーが削除されます。
次のセクションでは、実稼働環境でのCSPの影響を監視します。
ステップ5—違反の監視
CSPを設置したら、使用後はその効果を監視する必要があります。 たとえば、本番環境で正当なソースを許可するのを忘れた場合、または攻撃者がXSS攻撃ベクトルを悪用しようとしている場合(これを特定してすぐに停止する必要があります)。
何らかの形のアクティブなレポートが用意されていないと、これらのイベントを知る方法はありません。 これが、report-to
ディレクティブが存在する理由です。 CSPに基づいてアクションを実行する必要がある場合に、ブラウザーがJSON形式の違反レポートをPOSTする場所を指定します。
このディレクティブを使用するには、 ReportingAPIのエンドポイントを指定するヘッダーを追加する必要があります。
Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"http://your_server_ip:5500/__cspreport__"}],"include_subdomains":true}
それが設定されたら、report-to
ディレクティブで次のようにグループ名を指定します。
report-to csp-endpoint;
server.js
ファイルの更新された部分と変更点は次のとおりです。 Report-To
ヘッダーの<your_server_ip>
プレースホルダーは、必ず実際のサーバーIPアドレスに置き換えてください。
. . .
app.use(function (req, res, next) {
res.setHeader(
'Report-To',
'{"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"https://<your_server_ip>:5500/__cspreport__"}],"include_subdomains":true}'
);
res.setHeader(
'Content-Security-Policy-Report-Only',
"default-src 'self'; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://images.unsplash.com; script-src 'self' https://cdn.jsdelivr.net/npm/[email protected]/dist/ 'sha256-INJfZVfoUd61ITRFLf63g+S/NJAfswGDl15oK0iXgYM='; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css; frame-src 'self' https://www.youtube.com https://youtube.com; report-to csp-endpoint;"
);
next();
});
. . .
report-to
ディレクティブは、現在非推奨となっているreport-uri
ディレクティブを置き換えることを目的としていますが、ほとんどのブラウザーはまだサポートしていません(2020年11月現在)。 したがって、現在のブラウザとの互換性と、将来のブラウザリリースのサポートとの互換性を確保するために、CSPでreport-uri
とreport-to
の両方を指定する必要があります。 後者がサポートされている場合、前者は無視されます。
. . .
app.use(function (req, res, next) {
res.setHeader(
'Report-To',
'{"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"https://your_server_ip:5500/__cspreport__"}],"include_subdomains":true}'
);
res.setHeader(
'Content-Security-Policy-Report-Only',
"default-src 'self'; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://images.unsplash.com; script-src 'self' https://cdn.jsdelivr.net/npm/[email protected]/dist/ 'sha256-INJfZVfoUd61ITRFLf63g+S/NJAfswGDl15oK0iXgYM='; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css; frame-src 'self' https://www.youtube.com https://youtube.com; report-to csp-endpoint; report-uri /__cspreport__;"
);
next();
});
. . .
/__cspreport__
ルートはサーバーにも存在する必要があります。 次のようにこれをファイルに追加します。
. . .
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname + '/index.html'));
});
app.post('/__cspreport__', (req, res) => {
console.log(req.body);
});
. . .
レポートペイロードのContent-Type
をapplication/csp-report
として送信するブラウザもあれば、application/json
を使用するブラウザもあります。 report-to
ディレクティブがサポートされている場合、Content-Type
はapplication/reports+json
である必要があります。
考えられるすべてのContent-Type
値を考慮するには、Expressサーバーでいくつかの構成をセットアップする必要があります。
. . .
app.use(
bodyParser.json({
type: ['application/json', 'application/csp-report', 'application/reports+json'],
})
);
. . .
この時点で、CSP違反は/__cspreport__
ルートに送信され、その後端末に記録されます。
現在のCSPに準拠していないソースからリソースを追加するか、次のようにindex.html
ファイルのインラインスクリプトを変更することで、試してみることができます。
. . .
<script>
new Vue({
el: '#vue',
render(createElement) {
return createElement('h1', 'Hello World!');
},
});
console.log("Hello")
</script>
. . .
スクリプトのハッシュがCSPヘッダーに含めたものと異なるため、これにより違反がトリガーされます。
report-uri
を使用したブラウザからの一般的な違反レポートは次のとおりです。
{
'csp-report': {
'document-uri': 'http://localhost:5500/',
referrer: '',
'violated-directive': 'script-src-elem',
'effective-directive': 'script-src-elem',
'original-policy': "default-src 'self'; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://images.unsplash.com; script-src 'self' https://cdn.jsdelivr.net/npm/[email protected]/dist/ 'sha256-INJfZVfoUd61ITRFLf63g+S/NJAfswGDl15oK0iXgYM='; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css; frame-src 'self' https://www.youtube.com https://youtube.com; report-uri /__cspreport__;",
disposition: 'report',
'blocked-uri': 'inline',
'line-number': 58,
'source-file': 'http://localhost:5500/',
'status-code': 200,
'script-sample': ''
}
}
このレポートの一部は次のとおりです。
document-uri
:違反が発生したページ。referrer
:ページのリファラー。blocked-uri
:ページのポリシーに違反したリソース(この場合はinline
スクリプト)。line-number
:インラインコードが始まる行番号。violated-directive
:違反した特定のディレクティブ。 (この場合、script-src-elem
は、script-src
にフォールバックします。)original-policy
:ページの完全なポリシー。
ブラウザがreport-to
ディレクティブをサポートしている場合、ペイロードは次のような構造になっている必要があります。 同じ情報を保持しながら、report-uri
ペイロードとどのように異なるかに注意してください。
[{
"age": 16796,
"body": {
"blocked-uri": "https://vimeo.com",
"disposition": "enforce",
"document-uri": "https://localhost:5500/",
"effective-directive": "frame-src",
"line-number": 58,
'original-policy': "default-src 'self'; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://images.unsplash.com; script-src 'self' https://cdn.jsdelivr.net/npm/[email protected]/dist/ 'sha256-INJfZVfoUd61ITRFLf63g+S/NJAfswGDl15oK0iXgYM='; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css; frame-src 'self' https://www.youtube.com https://youtube.com; report-uri /__cspreport__;",
"referrer": "",
"script-sample": "",
"sourceFile": "https://localhost:5500/",
"violated-directive": "frame-src"
},
"type": "csp",
"url": "https://localhost:5500/",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36"
}]
注:report-to
ディレクティブは、安全なコンテキストでのみサポートされます。つまり、有効なHTTPS証明書を使用してExpressサーバーをセットアップする必要があります。そうしないと、テストまたはテストができません。これを使って。
このセクションでは、問題を迅速に検出して修正できるように、サーバーにCSP監視を正常に設定しました。 次のステップで最終ポリシーを適用して、このチュートリアルを終了しましょう。
ステップ6—最終ポリシーの公開
CSPが正しくセットアップされていることを確認したら(理想的には、レポートのみのモードで数日または数週間本番環境に置いた後)、CSPヘッダーをContent-Security-Policy-Report-Only
からContent-Security-Policy
。
違反を報告することに加えて、これは許可されていないリソースがページ上で実行されるのを防ぎ、訪問者にとってより安全な体験につながります。
server.js
ファイルの最終バージョンは次のとおりです。
const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
const app = express();
app.use(function (req, res, next) {
res.setHeader(
'Report-To',
'{"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"http://your_server_ip:5500/__cspreport__"}],"include_subdomains":true}'
);
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://images.unsplash.com; script-src 'self' https://cdn.jsdelivr.net/npm/[email protected]/dist/ 'sha256-INJfZVfoUd61ITRFLf63g+S/NJAfswGDl15oK0iXgYM='; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css; frame-src 'self' https://www.youtube.com https://youtube.com; report-to csp-endpoint; report-uri /__cspreport__;"
);
next();
});
app.use(
bodyParser.json({
type: [
'application/json',
'application/csp-report',
'application/reports+json',
],
})
);
app.use(express.static(path.join(__dirname)));
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname + '/index.html'));
});
app.post('/__cspreport__', (req, res) => {
console.log(req.body);
});
const server = app.listen(process.env.PORT || 5500, () => {
const { port } = server.address();
console.log(`Server running on PORT ${port}`);
});
ブラウザのサポートCSPヘッダーは、非標準のX-Content-Security-Policy
ヘッダーを代わりに使用するInternetExplorerを除いて、すべてのブラウザでサポートされています。 IEをサポートする必要がある場合は、応答ヘッダーでCSPを2回発行する必要があります。
CSP仕様(レベル3)の最新バージョンでも、現時点では十分にサポートされていない新しいディレクティブがいくつか導入されています。 例には、script-src-elem
およびprefetch-src
ディレクティブが含まれます。 これらのディレクティブを設定するときは、適切なフォールバックを使用して、まだ追いついていないブラウザーで保護がアクティブなままであることを確認してください。
結論
この記事では、Node.jsアプリケーションに効果的なコンテンツセキュリティポリシーを設定し、違反を監視しました。 古いまたはより複雑なWebサイトを使用している場合は、すべてのベースをカバーするより広範なポリシー設定が必要になります。 ただし、徹底的なポリシーを設定すると、攻撃者がWebサイトを悪用してユーザーデータを盗むことが非常に困難になるため、努力する価値があります。
このチュートリアルの最終的なコードは、このGitHubリポジトリにあります。
セキュリティ関連の記事については、セキュリティトピックページをご覧ください。 Node.jsの操作について詳しく知りたい場合は、Node.jsシリーズのコーディング方法をご覧ください。