安全のためのヘッダーと CSP

レッスン 2-8

CSP は、書き方しだいで何も守りません。

目安 60 分 トラック 2 Web アプリケーションの 8 本目

読み終えたらミドルウェアで付けたヘッダーが、静的な配信で抜ける理由を説明できる

この頁の流れ

  1. 1なぜ通るのか
  2. 2脆いコードと直したコード
  3. 3ログでの見え方と検知
  4. 4直し方と確かめ方
  5. 5関わる事例
  6. 6確認クイズ
  7. 7説明課題
  8. 8一次資料
  9. 9次へ

Content Security Policy (CSP) は、ページが読み込んでよい物と送ってよい先をブラウザに伝える決まりです。何でも許す書き方では何も守らず、正しく書いても、付かない経路があればその頁は素通しです。nonce を使った書き方、HSTS が守らない最初の 1 回、ミドルウェアで付けたヘッダーが静的な配信で抜ける理由、記録での見え方と、直ったことを試験で確かめる方法までを扱います。

出典を確かめた日 2026年10月27日

なぜ通るのか

Web のアプリに、安全のためのヘッダー (応答に付けて、ブラウザに守り方を伝える行) をまとめて付けることにしたとします。アプリの部品の「ミドルウェア」(すべての要求の前後に挟まる処理) に CSP と HSTS を書き、トップの頁で確かめると、ちゃんと付いています。

ところが、同じサイトの別の頁では付いていないことがあります。画像や HTML を配る静的な配信、見つからない頁の 404、別の住所への転送は、ミドルウェアを通らずに返ることがあるからです。ヘッダーは「書いたか」ではなく「その応答に付いたか」で効きます。

エスケープの場所で XSS を止める話は「クロスサイトスクリプティング」のレッスンで扱いました。CSP はその後ろに置く 2 枚目の守りです。このレッスンは、その 2 枚目が効かなくなる所に絞ります。

CSP は何を伝えるか

CSP は、W3C の Content Security Policy Level 3 が定める決まりです。応答のヘッダーで、項目 (ディレクティブ) ごとに許す相手を並べます。よく使う項目は次の通りです。

項目決めること
default-srcほかの項目で決めていない読み込みと送り先の既定
script-src動かしてよいスクリプトの出どころ
connect-srcスクリプトから通信してよい先
object-src古いプラグインの埋め込み。ふつうは none にする
base-uri頁の中の相対の住所の基準を変える base の行を許す先
form-actionフォームを送ってよい先。default-src を引き継がない
frame-ancestorsこの頁を枠の中に埋め込んでよい相手

frame-ancestors は、古い X-Frame-Options に代わる物です。OWASP の HTTP Headers Cheat Sheet は、対応するブラウザでは frame-ancestors が X-Frame-Options を置き換えると書いています。同じ Cheat Sheet は、X-Content-Type-Options に nosniff を付け、ブラウザが中身の種類を推し量って別の物として扱わないようにすることも勧めています。CSP と並べて、同じ出口で付けます。

何も守らない CSP

script-src に「unsafe-inline」(頁の中に直に書いたスクリプトを全部許す) や、どこでも許す「*」を入れると、XSS で差し込まれたスクリプトもそのまま動きます。ヘッダーは付いていても、守りとしては空です。

頁の中に直に書くスクリプトを許したいときは、nonce (応答ごとに作る使い捨ての値) を使います。CSP Level 3 は、nonce は応答のたびに新しく作り、128 ビット以上で、暗号に使える乱数から作るべきだとしています。攻め手は次の応答の nonce を当てられないので、差し込んだスクリプトに正しい nonce を付けられません。nonce を固定の値にすると、1 度見られただけで意味を失います。

もう 1 つ、CSP Level 3 は、default-src の無い CSP は情報の持ち出しを抑えられないと書いています。個別の項目では決められない種類の通信があるからです。送り先を絞りたいなら、default-src から書きます。ただし、フォームの送り先を決める form-action は default-src を引き継がないので、別に書きます。書かないと、差し込まれたスクリプトがフォームを作って外へ送れます。

付け方で抜ける物

CSP は HTML の meta の行でも伝えられますが、CSP Level 3 によると、meta では Report-Only の形と、report-uri、frame-ancestors、sandbox の項目が効きません。埋め込みを止めたいなら、ヘッダーで付けます。

HSTS (Strict-Transport-Security、このサイトには HTTPS でだけつなぐようブラウザに覚えさせる行) にも、守らない所があります。RFC 6797 によると、ブラウザは HTTP でつないだときに届いた HSTS の行を無視します。そして、初めて訪れるときに http の住所で入ると、最初の 1 回は守られないまま通信します。仕様はこれを最初のつなぎの弱さとして挙げています。

脆いコードと直したコード

ラボの中で比べます。Python に付いている wsgiref で小さな Web のアプリを 127.0.0.1 に立て、外とは通信しません。

何でも許すか、nonce で許すか

脆いコード 何でも許す CSP

記録

Content-Security-Policy:
  script-src 'self' 'unsafe-inline' *
直したコード 応答ごとの nonce で許す

記録

Content-Security-Policy:
  default-src 'self';
  script-src 'nonce-{応答ごとの値}';
  object-src 'none';
  base-uri 'none';
  form-action 'self';
  frame-ancestors 'none'

直した方は、まず default-src で既定を自分のサイトだけにします。スクリプトは、応答ごとに作った nonce が付いた物だけを動かします。頁の中のスクリプトの行にも、同じ値を nonce の属性として付けます。nonce は secrets.token_urlsafe(16) のように、暗号に使える乱数から 16 バイト (128 ビット) 以上で作ります。

アプリの経路にだけ付けるか、出口の 1 か所で付けるか

脆いコード 描いた頁にだけ付ける

Python

def app(req):
    p = req.path
    if p.startswith("/static/"):
        return serve_file(p)
    res = render(req)
    res.headers.update(SECURITY)
    return res
直したコード 出口の 1 か所で全部に付ける

Python

def app(req):
    p = req.path
    if p.startswith("/static/"):
        res = serve_file(p)
    else:
        res = render(req)
    res.headers.update(SECURITY)
    return res

脆い方は、静的なファイルを返す分岐が、ヘッダーを付ける行より前に抜けています。/static/ の下に置いた HTML の頁は、CSP も HSTS も無いまま配られます。直した方は、どの分岐を通っても、出口の 1 か所で付けます。

実際のサービスでは、静的なファイルはアプリを通らず、Web サーバーや CDN、置き場のサービスから直に配られることがよくあります。そのときは、その層の設定でも同じヘッダーを付けます。アプリの中をどれだけ直しても、アプリを通らない応答には届かないからです。

ログでの見え方と検知

経路ごとに、応答の頭を見比べます。ラボでは curl の -I で、頭だけを取ります。

経路ごとに頭を見比べる

記録

$ B=http://127.0.0.1:8000
$ curl -sI $B/ | grep -i content-sec
content-security-policy: default-src ...

$ curl -sI $B/static/a.html \
    | grep -i content-sec
(何も出ない)

下の段のように、同じサイトなのに付いていない経路があれば、その経路はミドルウェアを通っていません。守る側は次を見張ります。

見る物なぜ効くか
経路の種類ごとの応答の頭頁、静的なファイル、404、転送、エラーの頁で抜けを見つけられる
CSP の違反の知らせ止められた読み込みや送り先が分かる。急に増えたら差し込みを疑う
script-src の中の unsafe-inline と *書いた時点で、何も守らない CSP だと分かる
固定の nonce応答をまたいで同じ値なら、nonce の意味が無い

いきなり止める形で出すと、正しい頁まで壊すことがあります。CSP には止めずに知らせるだけの Report-Only の形があるので、まずそちらで出し、違反の知らせを見てから止める形へ移します。進め方は XSS のレッスンの検知の節と同じです。

直し方と、直ったことの確かめ方

自分の PC の中だけで行います。待ち受けは 127.0.0.1 に固定し、外のサービスには向けません。

  1. 作業用のフォルダに、wsgiref で動く小さなアプリと、static/a.html を置く
  2. 脆い app を 127.0.0.1 で動かし、curl の -I でトップの頁と static/a.html と存在しない頁の頭を取り、CSP が付く経路と付かない経路があることを確かめる
  3. 直したコードに置き換え、CSP を nonce の形にし、3 つの経路すべてに同じヘッダーが付くことを確かめる
  4. 頁を 2 回取り、nonce が毎回変わることを確かめる
  5. 下の試験を足し、脆い分岐に戻すと試験が落ちることも確かめる
  6. 終わったら、アプリを止め、作業用のフォルダごと消す
直ったことを固める試験

Python

CSP = "Content-Security-Policy"
PATHS = ["/", "/static/a.html",
         "/no-such-page"]

@pytest.mark.parametrize("path", PATHS)
def test_every_path_has_csp(path):
    h = headers_for(path)
    csp = h.get(CSP, "")
    assert "default-src" in csp
    assert "unsafe-inline" not in csp

def test_nonce_changes():
    a = headers_for("/")
    b = headers_for("/")
    assert a[CSP] != b[CSP]

headers_for は、アプリを wsgiref の試験用の道具でその場で呼び、応答の頭を辞書で返す小さな関数です。試験は「どの経路にも同じ守りが付く」ことと「nonce が使い回されない」ことを固定します。あとで誰かが新しい経路を足して付け忘れても、ここに 1 行足せば止まります。

本番に出したあとは、同じ見比べを、CDN や置き場のサービスから直に配られる住所にも当てます。HSTS は HTTPS の応答でしか効かないので、本番の HTTPS の住所で確かめます。

関わる事例

英国の個人情報の監督機関 ICO の制裁の通知によると、2018年6月22日から9月5日にかけて、ブリティッシュ・エアウェイズのサイトのスクリプトのファイルが書き換えられ、決済の頁で入力されたカードの情報が、攻め手が持つ別のドメイン (BAways.com) へ送られました。ICO は、こうした悪意のある動きを見つける手立てを BA は取れたはずだとし、その例として、コードの書き換えを見つけて知らせるファイルの完全性の見張りを挙げています。

ICO の通知は CSP には触れていません。ここからはこのレッスンの見立てです。書き換えられたのはサイト自身のスクリプトなので、script-src で出どころを絞っても、この動きは止まりません。一方で、default-src、connect-src、form-action で送り先を自分のサイトと決めた相手だけにしていれば、別のドメインへの送信の一部を止め、違反の知らせで早く気づく手がかりになりえます。CSP Level 3 も、通信してよい先を絞った CSP は持ち出しを抑えうるとする一方、個別の項目だけを絞っても、ほかの種類の通信で持ち出せる例を挙げています。CSP は書き換えを見つける見張りの代わりではなく、並べて置く物です。

確認クイズ

1 問目

ミドルウェアで CSP を付けています。トップの頁では付いていましたが、/static/ の下の HTML では付いていませんでした。まず何を疑いますか。

答えだと思う選択肢を押すと、その場で正誤と理由が開きます。

2 問目

頁の中に直に書いたスクリプトを動かしつつ、XSS で差し込まれたスクリプトは止めたい。どう書くのがよいですか。

答えだと思う選択肢を押すと、その場で正誤と理由が開きます。

説明課題

次のことを、自分の言葉で 3 行にまとめてください。書けたら、このレッスンは修了です。

  • ミドルウェアで付けたヘッダーが、静的な配信で抜ける理由
模範解答を見る

ミドルウェアは、アプリが返す応答にだけ挟まる処理で、ヘッダーはそこを通った応答にしか付かない。静的なファイルは、アプリの手前の分岐や、Web サーバー、CDN、置き場のサービスから直に配られることが多く、ミドルウェアを通らない。だからヘッダーは出口の 1 か所か配る層で付け、経路の種類ごとに応答の頭を見比べて確かめる。

一次資料

どれも 2026年10月27日に開いて、題と中身を確かめました。

次へ

次のレッスンは「偵察と攻撃面の洗い出し」です (準備中)。自分のドメインに、把握していない頁や経路がどれだけあるかを、公開の記録から洗い出す方法を扱います。

次のレッスン 3-1 偵察と攻撃面の洗い出し

このレッスンにつながる物

トラック 2 Web アプリケーションの中で

前のレッスン 2-7 危ないデシリアライズ

学んだことを次につなげる

ゆめさくセキュリティは、攻撃の仕組みを知り、自分の PC の中で壊して直し、守る側の判断を身につけるための学びの場です。

ラボはすべて自分の PC の中 (127.0.0.1) で閉じます。自分が管理していない相手に試すことは扱いません。