Content Security PolicyでXSS攻撃を軽減する

:bookmark: このガイドでは、Discourse におけるクロスサイトスクリプティング(XSS)攻撃を緩和するために、コンテンツセキュリティポリシー(CSP)を使用する方法について説明します。CSP の基礎、設定、およびベストプラクティスをカバーしています。

:person_raising_hand: 必要なユーザーレベル:管理者

まとめ

コンテンツセキュリティポリシー(CSP)は、クロスサイトスクリプティング(XSS)やその他のインジェクション攻撃から保護するための、Discourse における重要なセキュリティ機能です。このガイドでは、CSP の基礎、Discourse での実装方法、およびサイトでの設定方法について解説します。

コンテンツセキュリティポリシーとは?

コンテンツセキュリティポリシーは、クロスサイトスクリプティング(XSS)やデータインジェクション攻撃など、特定の種類の攻撃を検出および緩和するための追加のセキュリティ層です。CSP は、信頼できるコンテンツソースを指定し、ブラウザに対してそれらの信頼できるソースからのリソースのみを実行またはレンダリングするように指示することで機能します。

XSS は依然として最も一般的な Web の脆弱性の一つです。CSP を実装することで、Discourse は信頼できるソースからのスクリプトのみをロードおよび実行することを許可し、XSS 攻撃のリスクを大幅に低減します。

Discourse の CSP 実装

Discourse バージョン 3.3.0.beta1 以降、Discourse は「strict-dynamic」CSP を実装しています。このアプローチでは、script-src ディレクティブに単一の nonce- 値と strict-dynamic キーワードを使用します。コアおよびテーマ内のすべての初期の <script> タグには、自動的に適切な nonce= 属性が割り当てられます。

デフォルトのポリシーには、以下のディレクティブが含まれています。

  • script-src:JavaScript の有効なソースを指定
  • worker-src:ServiceWorker スクリプトの有効なソースを指定
  • object-src:プラグイン(Flash、Java など)の実行をブロック
  • base-uri<base> 要素の URL を制限
  • manifest-src:Web アプリマニフェストの URL を制限
  • frame-ancestors:どのサイトが iframe 内に Discourse インスタンスを埋め込むことができるかを制御
  • upgrade-insecure-requests:HTTP リクエストを自動的に HTTPS にアップグレード(force_https が有効な場合に含まれる)

Discourse での CSP の設定

利用可能な設定

  • content_security_policy:CSP の有効化または無効化(デフォルト:オン)
  • content_security_policy_report_only:CSP レポートオンリーモードの有効化(デフォルト:オフ)
  • content_security_policy_script_src:デフォルトの script-src ディレクティブ を拡張可能
  • content_security_policy_frame_ancestorsframe_ancestors ディレクティブ を有効化(デフォルト:オン)

CSP の有効化方法

  1. 管理パネルに移動
  2. セキュリティ設定へ移動
  3. content_security_policy 設定を見つけ、有効になっていることを確認

CSP を完全に有効にする前に、潜在的な問題を確認するために、CSP レポートオンリーモードから開始することをお勧めします。

  1. content_security_policy_report_only 設定を有効化
  2. ブラウザのコンソールで CSP 違反を監視
  3. 必要に応じて CSP を拡張し、正当な違反に対処
  4. 誤検知がないことを確信したら、レポートオンリーモードを無効化し、CSP を完全に有効化

デフォルトの CSP の拡張

追加のスクリプトソースを許可する必要がある場合は、content_security_policy_script_src 設定を使用して script-src ディレクティブを拡張できます。以下を追加できます。

  • ハッシュソース
  • 'wasm-unsafe-eval'
  • 'unsafe-eval'(注意して使用してください)

例:

'sha256-QFlnYO2Ll+rgFRKkUmtyRublBc7KFNsbzF7BzoCqjgA=' 'unsafe-eval'

:warning: 'unsafe-eval' や他の寛容なディレクティブを追加する際は注意してください。これらは CSP の効果を低下させる可能性があります。

CSP とサードパーティとの統合

Google タグマネージャー、Google アナリティクス、広告サービスなどのサードパーティサービスを使用する場合は、CSP 設定を調整する必要がある場合があります。Discourse バージョン 3.3.0.beta1 以降のほとんどの場合、‘strict-dynamic’ CSP の実装により、追加の設定なしで外部スクリプトが動作します。

問題が発生した場合は、以下を行う必要がある場合があります。

  1. ブラウザのコンソールを監視して、必要なスクリプトソースを特定
  2. 必要なソースを content_security_policy_script_src 設定に追加
  3. 外部リソースをロードする広告サービスなどの複雑な統合の場合、ドメイン間レンダリングを有効にする必要があるかもしれません(これを行う discourse-adplugin の例 PR を参照)。

ベストプラクティス

  1. 潜在的な問題を確認するために、CSP レポートオンリーモードから開始
  2. 正当な違反を解決するにつれて、CSP を徐々に強化
  3. CSP 設定を定期的にレビューし、必要に応じて調整
  4. 'unsafe-eval''wasm-unsafe-eval' のような寛容なディレクティブを追加する際は注意
  5. 最新の CSP の改善を活用するために、Discourse インスタンスを最新の状態に保つ

よくある質問

Q: CSP 違反レポートが多数表示されています。心配すべきですか?
A: 多くの CSP 違反は誤検知であり、ブラウザ拡張機能や他の無関係なスクリプトが原因であることが多いです。サイトの機能に関連する違反に対処することに集中してください。

Q: Google AdSense や他の広告ネットワークと CSP を併用できますか?
A: はい、ただしより寛容な CSP 設定を使用する必要がある場合があります。レポートオンリーモードから開始し、報告された違反に基づいて設定を調整してください。

Q: CSP の問題をトラブルシューティングするにはどうすればよいですか?
A: ブラウザの開発者ツールを使用して、CSP 違反メッセージをコンソールで監視してください。これにより、どのリソースがブロックされているか、その理由を特定できます。

追加リソース

「いいね!」 56
Adsense Not Working after Recent Discourse Update
Discourse 2.2.0.beta6 Release Notes
Adding statcounter code
How to install npm packages in custom themes/plugins
How to restart Discourse after server reboot?
Interactive SVG using <object>?
Video Upload to YouTube and Vimeo using Theme Component
Embed HTML5 player for MP3 file
2.5.0.beta5 breaks retort plugin
Should I load third-party libraries from vendor or cdn?
Word Cloud plugin
Embed widget within text in a topic
Discourse Intercom (Advanced)
Push custom events to Google Tag Manager and Analytics
Google Tag Manager and Discourse CSP (Content Security Policy)
Cookie Consent, GDPR, and Discourse
A strange question about google ad display in my site
How to pass a component setting as a value to an attribute?
Need help integrating code wrote on Edittext to the Discourse
Issue with Activate Account Page After Update to 3.4.0 (Blank Page)
"Unsafe JavaScript attempt to initiate navigation"
(Superseded) Experimenting with a 'strict-dynamic' Content Security Policy (CSP)
Can't get script tag to work in landing pages plugin due to content-security-policy
How to embed Razorpay subscription button with CSP restrictions
Any approved method for adding Javascript before body close?
Can I add a snippet to the header?
JS script is not loading
How do we fire scripts after topic HTML is rendered in DOM?
Iframe issue without URL
Where to place ad script?
We couldn't find the code on your site
Javascript not working in customised areas
Difficulties in correctly adding external JavaScript
Report Only CSP Violations
Nginx config in Discourse Docker?
EPN Smart Links
Discourse 2.2.0.beta9 Release Notes
"Refused to load the script" when adding adsense
Why Cookie Consent Doesn't Show Up?
[DigitalOcean] hostname having "www" in A records showing blank page
[DigitalOcean] hostname having "www" in A records showing blank page
Communities with embedded Twitter Feeds
How to add analytics and pixel scripts avoiding Content Security Policy (XSS)
When install html script facing issue?
Discourse 2.4.0.beta10 Release Notes
Add CSP sources to the plugin
IP does not redirect to domain, domain shows white page
DISCOURSE_CDN_URL causes content security policy violations?
Header content is missing due to CSP
How to insert something right after <head>?
How can I embed tracking JS into Discourse
How do I integrate ? cookiebot.com in meinen Forum?
Adding Cookie Consent Banner
Confused about remotely loaded javascript content
User input validation
Custom JS script in theme component not loading

I added a note about this to our public security.md file :tada:

「いいね!」 13

As of this commit, we’ve turned off CSP violations reports by default because the vast majority of the reported violations are false positives.

To illustrate this, here is a screenshot of logs from a site running Discourse with CSP enabled and reporting enabled (filtered using “CSP Violation”):

All of the reported violations are not related to the site’s code:

  • violations with ‘minisrclink.cool’ or ‘proxdev.cool’ in the URL have nothing to do with Discourse, they’re likely coming from a browser extension
  • the Google Analytics violation reports are also not legitimate. They are triggered by Firefox in privacy mode, or Firefox with a privacy extension enabled (like DuckDuckGo Privacy Essentials).
  • Violations with ‘inline’, ‘data’ or ‘about’ are triggered by extensions as well. It’s not shown in the screenshot above, but these violations have some more details in the env tab of the log. In there, under script-sample, some of these violations had code like BlockAdBlock or window.klTabId_kis or AG_onLoad, which come from the AdBlock, Kaspersky, and AdGuard extensions, respectively. (I found this repo: CSP-useful/csp-wtf/README.md at master · nico3333fr/CSP-useful · GitHub very useful in helping explain some of these reports.) Some of these violations will have safari-extension or user-script in the source-file variable (again, in env), so that points to Safari extensions as the culprit for the violation.

In other words, there’s a lot of noise in CSP violation reports, so it’s not useful to log them at all times. They might be helpful while you are configuring CSP, but the reporting should be off during the normal operation of a site.

A few final notes: if you site is using a tag manager (like Google Tag Manager or Segment) you need to load the site in your browser, and carefully examine the violations in the console. These tools load third-party scripts from third-party domains and/or inline scripts so you need to carefully whitelist each of them using the source URL or the hash of the inline script (Chrome usefully includes the hash of inline scripts in the console error statement).

If your site uses an advertising service (like Google Ad Manager, Adsense, etc.) you probably will have to use a very permissive policy:

In the screenshot above, the policy allows any script from a https: source and any inline script. (In the future, this might be replaced by the strict-dynamic keyword, but as of this writing, strict-dynamic isn’t supported by Safari or Edge.)

「いいね!」 24

8 posts were split to a new topic: Protocol-less CDN URLs are problematic

Please note that Safari doesn’t understand some parts of CSP, and this is normal:

You can safely ignore the CSP errors in Safari, you’ll see those on all sites, it just means Safari doesn’t understand worker-src and report-sample .

I guess we need to wait for Safari to be updated?

「いいね!」 12

I’m having trouble configuring, which recommendation?

my forum: forum.meuxbox.com.br

link: White blank advertisement

「いいね!」 2

It depends on what URLs your ads are requesting. You can look at your browser’s console to see them.

See also the relevant section from the OP:

「いいね!」 4

https://meta.discourse.org/t/white-blank-advertisement/140098/3?u=eduardo_braga

this solves the problem of the error

「いいね!」 6

Feature-Policy](Permissions-Policy - HTTP | MDN) を追加していただけますか?

私はこれを 1 年以上使用しています(ホストは nginx)。

add_header Feature-Policy “geolocation ‘none’; midi ‘none’; notifications ‘self’; push ‘none’; sync-xhr ‘none’; microphone ‘none’; camera ‘none’; magnetometer ‘none’; gyroscope ‘none’; speaker ‘none’; vibrate ‘none’; fullscreen ‘none’; payment ‘none’;”;


Content-Security-Policy ヘッダーに以下の内容を追加するのは妥当でしょうか?これは私が正常に使用している設定です(ホストの nginx によって追加され、Discourse 組み込みの CSP の上に設定されています):

default-src 'none'
style-src 'self' domain 'unsafe-inline'
img-src https://*.domain.org data: blob: 'unsafe-inline'
font-src 'self' domain
connect-src 'self' domain
manifest-src 'self' domain
「いいね!」 3

仕様はまだドラフト段階であるため、現時点での実装は予定していません。あなたが挙げた Mozilla のウェブサイトにも以下のように記載されています。

Feature-Policy ヘッダーは現在実験的な段階にあり、いつでも変更される可能性があります。Web サイトで Feature Policy を実装する際は、この点に注意してください。

「いいね!」 8

vibrate 'self' - Androidでの「いいね」は、かすく短い振動をトリガーします。

「いいね!」 10

Discourse 2.6.0.beta1 をインストールしました。再設定は必要でしょうか?よろしくお願いいたします。

「いいね!」 1

必要ありません。これはデフォルトでオンになっています。外部リソースがブロックされているのを見て、それを実行させたい場合のみ、設定を変更する必要があります。

「いいね!」 4

2.6.0 ベータ 2 を実行しています。

フォーラムで以下のサービスを使用しています:

  • Google Tag Manager
  • Google Ad Manager
  • Google Ad Sense

現在、すべての未解決の問題を解決するまでの間、CSP をレポートモードのみに設定しています。

私の CSP 設定は以下の通りです:

現在の設定でも、依然として CSP エラーが大量に発生しています。いくつかは無視できるもののように見えます。しかし、CSP 設定にドメインを宣言しているにもかかわらず、このエラーは私を困惑させています。

何か見落としているのでしょうか?

CSP Violation: 'https://www.googletagmanager.com/gtm.js?id=GTM-T9ZW6PR'

バックトレース:

/var/www/discourse/app/controllers/csp_reports_controller.rb:9:in `create'
actionpack-6.0.3.2/lib/action_controller/metal/basic_implicit_render.rb:6:in `send_action'
actionpack-6.0.3.2/lib/abstract_controller/base.rb:195:in `process_action'
actionpack-6.0.3.2/lib/action_controller/metal/rendering.rb:30:in `process_action'
actionpack-6.0.3.2/lib/abstract_controller/callbacks.rb:42:in `block in process_action'
activesupport-6.0.3.2/lib/active_support/callbacks.rb:112:in `block in run_callbacks'
/var/www/discourse/app/controllers/application_controller.rb:340:in `block in with_resolved_locale'
i18n-1.8.5/lib/i18n.rb:313:in `with_locale'
/var/www/discourse/app/controllers/application_controller.rb:340:in `with_resolved_locale'
activesupport-6.0.3.2/lib/active_support/callbacks.rb:121:in `block in run_callbacks'
activesupport-6.0.3.2/lib/active_support/callbacks.rb:139:in `run_callbacks'
actionpack-6.0.3.2/lib/abstract_controller/callbacks.rb:41:in `process_action'
actionpack-6.0.3.2/lib/action_controller/metal/rescue.rb:22:in `process_action'
actionpack-6.0.3.2/lib/action_controller/metal/instrumentation.rb:33:in `block in process_action'
activesupport-6.0.3.2/lib/active_support/notifications.rb:180:in `block in instrument'
activesupport-6.0.3.2/lib/active_support/notifications/instrumenter.rb:24:in `instrument'
activesupport-6.0.3.2/lib/active_support/notifications.rb:180:in `instrument'
actionpack-6.0.3.2/lib/action_controller/metal/instrumentation.rb:32:in `process_action'
actionpack-6.0.3.2/lib/action_controller/metal/params_wrapper.rb:245:in `process_action'
activerecord-6.0.3.2/lib/active_record/railties/controller_runtime.rb:27:in `process_action'
actionpack-6.0.3.2/lib/abstract_controller/base.rb:136:in `process'
actionview-6.0.3.2/lib/action_view/rendering.rb:39:in `process'
rack-mini-profiler-2.0.4/lib/mini_profiler/profiling_methods.rb:78:in `block in profile_method'
actionpack-6.0.3.2/lib/action_controller/metal.rb:190:in `dispatch'
actionpack-6.0.3.2/lib/action_controller/metal.rb:254:in `dispatch'
actionpack-6.0.3.2/lib/action_dispatch/routing/route_set.rb:50:in `dispatch'
actionpack-6.0.3.2/lib/action_dispatch/routing/route_set.rb:33:in `serve'
actionpack-6.0.3.2/lib/action_dispatch/journey/router.rb:49:in `block in serve'
actionpack-6.0.3.2/lib/action_dispatch/journey/router.rb:32:in `each'
actionpack-6.0.3.2/lib/action_dispatch/journey/router.rb:32:in `serve'
actionpack-6.0.3.2/lib/action_dispatch/routing/route_set.rb:834:in `call'
/var/www/discourse/lib/middleware/omniauth_bypass_middleware.rb:68:in `call'
rack-2.2.3/lib/rack/tempfile_reaper.rb:15:in `call'
rack-2.2.3/lib/rack/conditional_get.rb:40:in `call'
rack-2.2.3/lib/rack/head.rb:12:in `call'
/var/www/discourse/lib/content_security_policy/middleware.rb:12:in `call'
/var/www/discourse/lib/middleware/anonymous_cache.rb:336:in `call'
rack-2.2.3/lib/rack/session/abstract/id.rb:266:in `context'
rack-2.2.3/lib/rack/session/abstract/id.rb:260:in `call'
actionpack-6.0.3.2/lib/action_dispatch/middleware/cookies.rb:648:in `call'
actionpack-6.0.3.2/lib/action_dispatch/middleware/callbacks.rb:27:in `block in call'
activesupport-6.0.3.2/lib/active_support/callbacks.rb:101:in `run_callbacks'
actionpack-6.0.3.2/lib/action_dispatch/middleware/callbacks.rb:26:in `call'
actionpack-6.0.3.2/lib/action_dispatch/middleware/actionable_exceptions.rb:17:in `call'
actionpack-6.0.3.2/lib/action_dispatch/middleware/debug_exceptions.rb:32:in `call'
actionpack-6.0.3.2/lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
logster-2.9.3/lib/logster/middleware/reporter.rb:43:in `call'
railties-6.0.3.2/lib/rails/rack/logger.rb:37:in `call_app'
railties-6.0.3.2/lib/rails/rack/logger.rb:28:in `call'
/var/www/discourse/config/initializers/100-quiet_logger.rb:19:in `call'
/var/www/discourse/config/initializers/100-silence_logger.rb:31:in `call'
actionpack-6.0.3.2/lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
actionpack-6.0.3.2/lib/action_dispatch/middleware/request_id.rb:27:in `call'
/var/www/discourse/lib/middleware/enforce_hostname.rb:22:in `call'
rack-2.2.3/lib/rack/method_override.rb:24:in `call'
actionpack-6.0.3.2/lib/action_dispatch/middleware/executor.rb:14:in `call'
rack-2.2.3/lib/rack/sendfile.rb:110:in `call'
actionpack-6.0.3.2/lib/action_dispatch/middleware/host_authorization.rb:76:in `call'
rack-mini-profiler-2.0.4/lib/mini_profiler/profiler.rb:200:in `call'
message_bus-3.3.1/lib/message_bus/rack/middleware.rb:61:in `call'
/var/www/discourse/lib/middleware/request_tracker.rb:176:in `call'
railties-6.0.3.2/lib/rails/engine.rb:527:in `call'
railties-6.0.3.2/lib/rails/railtie.rb:190:in `public_send'
railties-6.0.3.2/lib/rails/railtie.rb:190:in `method_missing'
rack-2.2.3/lib/rack/urlmap.rb:74:in `block in call'
rack-2.2.3/lib/rack/urlmap.rb:58:in `each'
rack-2.2.3/lib/rack/urlmap.rb:58:in `call'
unicorn-5.6.0/lib/unicorn/http_server.rb:632:in `process_client'
unicorn-5.6.0/lib/unicorn/http_server.rb:728:in `worker_loop'
unicorn-5.6.0/lib/unicorn/http_server.rb:548:in `spawn_missing_workers'
unicorn-5.6.0/lib/unicorn/http_server.rb:144:in `start'
unicorn-5.6.0/bin/unicorn:128:in `<top (required)>'
/var/www/discourse/vendor/bundle/ruby/2.6.0/bin/unicorn:23:in `load'
/var/www/discourse/vendor/bundle/ruby/2.6.0/bin/unicorn:23:in `<main>'

Env 1:

hostname	forums-web-only
process_id	27127
application_version	f2e14a3946b020ace5a368614f0da198cd17aa32
HTTP_HOST	forums.paddling.com
REQUEST_URI	/csp_reports
REQUEST_METHOD	POST
HTTP_USER_AGENT	Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0
HTTP_ACCEPT	*/*
HTTP_X_FORWARDED_FOR	74.76.45.218
HTTP_X_REAL_IP	74.76.45.218
time	1:44 pm

Env 2:

hostname	forums-web-only
process_id	27161
application_version	f2e14a3946b020ace5a368614f0da198cd17aa32
HTTP_HOST	forums.paddling.com
REQUEST_URI	/csp_reports
REQUEST_METHOD	POST
HTTP_USER_AGENT	Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0
HTTP_ACCEPT	*/*
HTTP_X_FORWARDED_FOR	66.58.144.146
HTTP_X_REAL_IP	66.58.144.146
time	1:39 pm

Env 3:

hostname	forums-web-only
process_id	27111
application_version	f2e14a3946b020ace5a368614f0da198cd17aa32
HTTP_HOST	forums.paddling.com
REQUEST_URI	/csp_reports
REQUEST_METHOD	POST
HTTP_USER_AGENT	Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
HTTP_ACCEPT	*/*
HTTP_REFERER	https://forums.paddling.com/t/need-advice-loading-a-kayak-onto-j-rac/60058
HTTP_X_FORWARDED_FOR	174.83.24.11
HTTP_X_REAL_IP	174.83.24.11
time	12:16 pm
「いいね!」 2

CSP のレポートには、誤検知が非常に多く含まれています。詳細については、私の 上記の返信 をご覧ください。

スクリーンショットで設定されているルールのほとんどは不要です。https:unsafe-inline のみで十分です。これらは https で始まるすべてのスクリプトと、すべてのインラインスクリプトを許可します。CSP ソース設定を整理し、CSP(レポート機能なし)を有効にしてみてください。これで動作するはずです。

「いいね!」 8

こんにちは、ご質問ありがとうございます。Content-Security-Policy: frame-ancestors ‘none’ ディレクティブは、元の投稿者(OP)が言及した「今後のアップデートに含まれる予定のもの」でしょうか?

このディレクティブを何らかの方法で追加することは可能でしょうか、それとも待つべきでしょうか?セキュリティ強化の作業を行っていたところ、このオンラインセキュリティツールから唯一の未対応項目(推奨事項)として指摘されました。プラットフォームに対する信頼がさらに高まりました。素晴らしい仕事をありがとうございます!

「いいね!」 5

よくわからないけど、どう思う? @xrav3nz

「いいね!」 4

今は気にしなくて大丈夫だと思います。

frame-ancestors は、Discourse/Rails が既に強制している X-Frame-Options ヘッダー に似ています。現在、このヘッダーは sameorigin に設定されており、これは CSP ディレクティブの self オプションとほぼ同じです。

個人的には、self 以外の特定のドメインをホワイトリストに登録する必要がある場合を除き、現時点で frame-ancestors を実装してもあまりメリットはないと思います。

「いいね!」 8

これに賛成です。

考えられるすべての脆弱性スキャナーの「問題」を追いかけることは、リスクとベネフィットの比率が非常に低いにもかかわらず、重要な機能を壊す結果になりかねません。

理論上は脆弱性として存在するが、実際にはほとんど悪用されない、あるいは極めて特定の状況下でのみ悪用可能な「脆弱性」が多数存在します。私の知る限り、このような潜在的な脆弱性が Discourse サイトで実際に悪用された例はありません。したがって、「理論上は脆弱性」であり、かつ重大な侵害を引き起こしたことがないものを「修正」しようとするのはお勧めしません。

これは数十年のサイバーセキュリティ専門家としての私の意見です。もしご関心があれば、サイバーセキュリティのリスク管理の基礎について喜んで議論します。

「いいね!」 5

CSP の frame-ancestors ディレクティブのサポートを追加しました。現時点では、content security policy frame ancestors サイト設定の背後で無効化されています。ドメインの追加は、いつも通り /admin/customize/embedding から行ってください。

このディレクティブは、次のリリースサイクルでデフォルトで有効になります。

「いいね!」 7