Astroサイトのセキュリティヘッダーを確認する―CSP・HSTSで守れる範囲と設定ミスの影響

HTTPセキュリティヘッダーは、ブラウザに読み込み元やHTTPS、iframe、MIME typeなどの制限を伝える仕組みです。Astro+Cloudflare Workers Static Assetsでの設定場所、CSP・HSTSの役割、GA4などを壊す設定ミス、過去の大規模事故から見える限界まで確認します。

執筆:ゆまラボ運営者

公開:
Astroサイトのセキュリティヘッダーを確認する―CSP・HSTSで守れる範囲と設定ミスの影響

WebサイトをHTTPSで公開していても、ブラウザ側で許可する動作はHTTPSだけでは決まらない。

外部のJavaScriptをどこから読み込めるか。

別サイトのiframeへページを埋め込めるか。

一度HTTPSで開いたドメインを、次回からHTTPで開かせないか。

レスポンスのContent-Typeをブラウザが勝手に推測してよいか。

こうした制御をブラウザへ伝えるのがHTTPセキュリティヘッダーになる。

ゆまラボは、Astroで生成した静的HTMLをCloudflare WorkersのStatic Assetsから配信する構成で始めた。その後、bringain.comを独自ドメインとしてWorkersへ接続し、HTTPSで公開している。

この構成ではWordPressのようにWebサーバー設定を直接変更する必要はない。

一方で、AstroがHTMLを生成しただけではContent-Security-PolicyStrict-Transport-Securityが自動的に十分な値で付くわけでもない。

Cloudflare Workers Static Assetsならpublic/_headersが使える。

ただし、セキュリティヘッダーは「付ければ多いほど安全」という設定ではない。特にCSPとHSTSは、設定を強くしすぎると正常なサイト機能まで止める。

ゆまラボではGoogle Analyticsも使っているため、CSPを適当に'self'だけで始めるとアクセス解析そのものを壊す可能性がある。

HTTPレスポンスヘッダーはHTMLの外側で効く#

ブラウザがページを開くと、HTML本文だけでなくHTTPレスポンスヘッダーも受け取る。

簡略化すると次の形。

HTTP/2 200
Content-Type: text/html; charset=UTF-8
Content-Security-Policy: ...
Strict-Transport-Security: ...
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin

<!doctype html>
<html>
...

HTMLへ、

<meta name="...">

を追加するのとは役割が違う。

Strict-Transport-SecurityのようにHTTPヘッダーでなければ機能しないものがある。Clickjacking対策に使うX-Frame-Options<meta>へ書いても効かない。

CSPは一部を<meta http-equiv>で指定できるが、frame-ancestorsなどmetaでは使えない指示もある。

配信時のレスポンスへ設定する方が、サイト全体へ一貫して適用しやすい。

HTTPレスポンスヘッダーをブラウザが受け取り、読み込み元やHTTPS、iframeなどを制限する概念図

※ 記事内の図は内容を整理するために作成した概念図です。各社の公式資料や実際の製品画面ではありません。

先に見るならCSP・HSTS・nosniff・frame制御#

セキュリティ関連のレスポンスヘッダーは多い。

小規模な静的ブログで最初から全部を追加するより、何を防ぎたいかを分けた方が扱いやすい。

Content-Security-Policy#

CSPは、ページが読み込めるスクリプト、画像、CSS、font、iframe、通信先などをブラウザ側で制限する。

例えば非常に単純化すると、

Content-Security-Policy: default-src 'self'

なら、基本的な取得元を同一originへ限定する。

さらに、

script-src
style-src
img-src
connect-src
frame-src
frame-ancestors
form-action

などを分けて指定できる。

XSSや第三者スクリプト侵害が起きた場合でも、攻撃コードが自由に外部リソースを読み込んだり、任意の送信先へ通信したりする範囲を狭められる。

ただしCSPはXSSそのものを消す仕組みではない。

OWASPも、CSPをXSS対策の追加レイヤーとして扱い、出力エンコードやsanitizeなどの基本対策を置き換えないよう案内している。

Strict-Transport-Security#

HSTSは、一度HTTPSで受け取ったブラウザに対して、そのドメインへ今後HTTPで接続せずHTTPSを使うよう指示する。

Strict-Transport-Security: max-age=31536000

のように有効期間を指定する。

HTTPからHTTPSへのリダイレクトだけでは、最初のHTTPアクセスが発生する余地がある。HSTSを記憶したブラウザではHTTP接続そのものを避けられる。

CloudflareもHSTSをHTTPS downgrade attackへの対策として案内している。

X-Content-Type-Options#

X-Content-Type-Options: nosniff

は、サーバーが返したMIME typeをブラウザが別の種類として推測するMIME sniffingを抑止する。

特にscriptやstylesheetでは、想定外のMIME typeを実行対象として扱わせないための追加防御になる。

iframeへの埋め込み制御#

古くから使われるのは、

X-Frame-Options: DENY

または、

X-Frame-Options: SAMEORIGIN

になる。

現在はCSPの、

Content-Security-Policy: frame-ancestors 'none'

の方が細かく指定できる。

他サイトから透明なiframeを重ね、利用者に別のボタンを押しているように見せるClickjackingへの対策になる。

OWASPは対応ブラウザではCSP frame-ancestorsを推奨し、X-Frame-Optionsを旧ブラウザ向けの防御として説明している。

Astro+Cloudflare Workers Static Assetsならpublic/_headersで設定できる#

Cloudflare WorkersのStatic Assetsでは、_headersというテキストファイルを使ってカスタムレスポンスヘッダーを指定できる。

AstroのCloudflare向け公式ドキュメントでも、

public/_headers

へ置けばビルド成果物へコピーされるとしている。

ゆまラボの現在のように静的HTMLをStatic Assetsから配信する構成なら、まずここが候補になる。

例えばCSPをまだ強制しない段階なら、次のような設定から各ヘッダーの影響を確認できる。

/*
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin
  X-Frame-Options: DENY
  Permissions-Policy: camera=(), microphone=(), geolocation=()

HSTSはHTTPS運用を確認したうえで別に追加する。

/*
  Strict-Transport-Security: max-age=31536000

includeSubDomainspreloadまで最初から付けるかは別判断になる。

CSPも、いきなり強制せず、

Content-Security-Policy-Report-Only: ...

で違反候補を確認してから本番のContent-Security-Policyへ移す方法がある。

Cloudflare自身もCSPのreport-onlyヘッダーをクライアント側セキュリティの監視に利用している。

SSRやWorker生成レスポンスでは同じ方法にならない#

ここはAstroで注意したい。

Cloudflareの_headersはStatic Assetsへ適用される。

Workerコードが生成したレスポンスには適用されない。

AstroでCloudflare adapterを使い、SSRやon-demand renderingへ変更した場合は、WorkerのResponseやAstro middleware側でヘッダーを付ける必要がある。

現在の構成が静的でも、将来API routeやサーバー側機能を追加したら「public/_headersを書いたからサイト全体で同じ」とは限らない。

設定場所はAstroそのものより、最終的にそのレスポンスを誰が返しているかで決まる。

CSPは強くするとGoogle Analyticsまで止める#

セキュリティヘッダーのデメリットが最も出やすいのはCSP。

ゆまラボではGoogle AnalyticsをAstroへ追加している

GA4は自サイトだけで完結せず、外部ドメインからJavaScriptを読み込み、計測データを外部へ送信する。

この状態で、

Content-Security-Policy: default-src 'self'

だけを強制すると、外部scriptや通信先がCSP違反になり、GA4が動かなくなる。

将来Google AdSenseを追加すれば、許可が必要なscript、frame、画像、通信先はさらに増える。

CSPでは次のような事故が起こりやすい。

CSPを強化

ページ自体は表示される

アクセス解析だけ止まる

または、

CSPを強化

外部fontや画像が拒否される

一部デザインだけ崩れる

サイトが真っ白にならなくても、裏側で機能が失敗することがある。

本番へ適用するときはDevToolsのConsoleとNetworkを見る。

CSP違反が出ているなら、必要な通信なのか、不必要な外部アクセスなのかを分ける。

「エラーが出たドメインを全部allowlistへ追加する」という直し方をすると、CSPを置いた意味が薄くなる。

HSTSは設定を戻しにくい#

HSTSも、値を付けるだけなら簡単。

問題はブラウザが設定を一定期間記憶すること。

例えば長いmax-ageを設定したあと、一時的にHTTPSを使えない構成へ戻しても、ブラウザはHTTPへ接続しない。

includeSubDomainsを付けると、まだHTTPS化していないサブドメインまで対象になる。

さらにpreloadを使う場合は、通常のレスポンスヘッダーより影響が長くなる。

CloudflareのHSTS設定画面でも、有効化前にHTTPSを維持できることなどの要件確認を求めている。

ゆまラボは現在bringain.comをHTTPSで公開しているが、それだけで即preloadまで有効にする理由にはならない。

HSTSは、

HTTPSが常時利用できる

影響するサブドメインを確認

max-ageを設定

必要ならincludeSubDomainsを検討

preloadはさらに別判断

くらいに分けた方が戻しやすい。

Referrer-PolicyやPermissions-Policyも副作用がある#

Referrer-Policyは、別ページへ移動するときに送るReferer情報の範囲を制御する。

OWASPでは、

Referrer-Policy: strict-origin-when-cross-origin

を推奨例として挙げている。

現在のブラウザにも比較的厳しい既定値があるため、昔のように「設定しないとURL全文が必ず外部へ送信される」という単純な話ではない。

それでもサイト側で方針を明示できる。

ただしno-referrerのように強く制限すれば、外部サービス側から見える参照元情報も減る。

アクセス解析や外部サイトとの連携でRefererを使っている場合は影響を確認する。

Permissions-Policyはcamera、microphone、geolocationなどのブラウザ機能を制限できる。

ブログで使わない機能を明示的に止める用途には使える。

一方、MDNではPermissions Policyを一部ブラウザで未対応の機能を含むものとして案内している。

将来、地図、動画、決済、iframe内の機能などを追加したときに、自分で設定したPolicyが原因で使えないこともある。

セキュリティヘッダーは、サイトの将来機能を知ったうえで値を決める必要がある。

大規模事故を見ると「1つのヘッダーで防ぐ」は危ない#

過去の大規模事故を見ると、ブラウザ側の防御を追加する意味は分かる。

同時に、セキュリティヘッダーだけで事故を防げるとは書けない。

British Airways#

2018年のBritish Airwaysの事故では、攻撃者がWebサイトのコードへ手を入れ、決済カード情報のコピーを攻撃者管理のドメインへ送信する状態が15日間続いた。

ICOの最終資料では429,612人が影響を受けたとしている。

このようなクライアント側のデータ送信に対し、厳格なCSPで接続先やform送信先を限定していれば、攻撃経路によっては被害を減らす追加防御になり得る。

ただし、ICOは複数のセキュリティ上の不足を問題としている。

「CSPがなかったから起きた事故」と単純化することはできない。

Ticketmaster#

Ticketmasterの2018年の侵害では、決済ページで利用していた第三者チャットボットのJavaScriptが問題になった。

ICO資料では、EEAで940万人が潜在的な影響対象として通知され、英国は150万人。カード情報などが対象になった。

ICOは、決済ページへ第三者scriptを入れる明確なリスクがある中で、Ticketmasterがlayered approachを十分に実装していなかったと指摘している。

CSPは第三者scriptの読み込み元や通信先を制限できる。

ただし、攻撃されたscriptの配信元をCSPですでに許可している場合、そのscript自体は動く。

通信先まで許可済みなら、CSPだけでは止められないこともある。

第三者scriptを減らす、内容を監視する、可能ならSubresource Integrityを使う、決済ページのような重要画面では依存関係を厳しくする。

ヘッダーはその一部になる。

この2件から見えるのは、「CSPを付ければ事故が起きない」ではなく、ブラウザにも制限を持たせるdefense in depthの考え方になる。

ゆまラボへ入れるならCSPを最後にする方が確認しやすい#

ゆまラボの現在の構成に合わせるなら、一度に強いCSPまで入れるより段階を分けたい。

比較的影響を確認しやすいのは、

X-Content-Type-Options
Referrer-Policy
frame制御
Permissions-Policy

になる。

HSTSは独自ドメインとサブドメインのHTTPS運用を確認してから。

CSPはGA4などの外部リソースを洗い出して、report-onlyから確認する。

実装時には、

トップページ
記事ページ
タグ・カテゴリページ
404ページ
GA4リアルタイム計測
XのOGP取得
画像・font
外部リンク

を確認したい。

特にGA4は、ページが普通に表示できても計測だけ止まる可能性がある。

アクセス解析カテゴリの記事として扱うなら、ここは外せない確認項目になる。

また、現在のゆまラボは静的配信なのでpublic/_headersが候補になるが、将来SSRやWorker処理へ移行した場合は同じファイルだけでは足りない。

セキュリティヘッダーはAstroのFrontmatterやコンポーネント設定ではなく、最終HTTP Responseの設定として管理する。

この境界を把握しておけば、サイト構成を変更した後も設定場所を追いやすい。

参考#