1. はじめに
この節は規範的ではありません。
[RFC1918] は 20 年以上にわたり 「プライベート」と 「パブリック」のインターネットアドレスの区別を規定してきましたが、ユーザーエージェントは 一方を他方から分離する点であまり進展していません。パブリック インターネット上の Web サイトは内部デバイスおよびサーバーにリクエストを行うことができ、 これにより、[DRIVE-BY-PHARMING]、[SOHO-PHARMING] および [CSRF-EXPLOIT-KIT] で記録されているような ユーザーのルーターへの攻撃を含む、多数の悪意ある振る舞いを可能にします。
ここでは、これらの種類の攻撃に対する軽減策を提案します。この軽減策では、 内部デバイスがパブリックインターネットからのリクエストに明示的にオプトインすることを 要求します。
1.1. 目標
包括的な目標は、ユーザーエージェントが、ユーザーのローカルイントラネット上で動作する デバイス、またはユーザーのマシン上で直接動作するサービスへの攻撃を、 不注意に可能にしてしまうことを防ぐことです。たとえば、次のものへの攻撃を軽減したいと考えています:
-
[SOHO-PHARMING] で概説されている、ユーザーのルーター。 ここで議論されている種類の攻撃に対しては、現状の CORS 保護は防御にならないことに注意してください。なぜなら、それらはCORS セーフリスト化メソッドおよびCORS セーフリスト化リクエストヘッダーのみに依存しているからです。 プリフライトはトリガーされず、攻撃者は実際にはレスポンスを読むことに関心がありません。 リクエストそのものが CSRF 攻撃だからです。
-
ユーザーのループバックアドレス上で Web インターフェイスを実行しているソフトウェア。 良くも悪くも、これはあらゆる種類のアプリケーションにとって一般的なデプロイ機構になりつつあり、 多くの場合、単に存在しない保護を前提にしています(最近の例については [AVASTIUM] および [TREND-MICRO] を参照)。
1.2. 例
1.2.1. デフォルトでセキュア
http://admin:admin@router.local/set_dns へナビゲートし、
さまざまな GET
パラメーターを渡すことで DNS 設定を変更できます。なんということでしょう!
幸い、MegaCorp Inc のルーターはパブリックインターネットからのリクエストに 関心を持っておらず、それらを有効にするための特別な努力もしていませんでした。これにより、 悪意あるリクエストがCORS プリフライトリクエストを生成し、それをルーターが無視するため、 脆弱性の範囲は大きく軽減されます。詳しく見てみましょう:
次の HTML を含む https://csrf.attack/ があるとします:
<iframe href="https://admin:admin@router.local/set_dns?server1=123.123.123.123"> </iframe>
router.local はマルチキャスト DNS [RFC6762]
の魔法によりルーターのアドレスに解決され、ユーザーエージェントはそれをプライベートとして記録します。csrf.attack は
パブリックアドレスに解決されたため、
リクエストはCORS プリフライトリクエストをトリガーします:
OPTIONS /set_dns?... HTTP/1.1 Host: router.local Access-Control-Request-Method: GET Access-Control-Request-Private-Network: true ... Origin: https://csrf.attack
ルーターはこの OPTIONS リクエストを受け取り、いくつかの安全なレスポンスを
返すことができます:
-
OPTIONSをまったく理解しない場合、50Xエラーを返すことができます。 これによりプリフライトは失敗し、実際のGETは決して発行されません。 -
OPTIONSを理解する場合でも、レスポンスにAccess-Control-Allow-Private-Networkヘッダーを含めないことができます。 これによりプリフライトは失敗し、実際のGETは決して発行されません。 -
クラッシュすることもできます。クラッシュは洗練されてはいませんが、かなり安全です。
1.2.2. オプトイン
パブリックインターネット上の Web サイトがデバイスにリクエストを行うと、 ユーザーエージェントはリクエスト元がパブリックであり、 ルーターがプライベートであると判断します。これは、上記と同様にリクエストが CORS プリフライトリクエストをトリガーすることを意味します。
デバイスは、プリフライトリクエストへのレスポンスで適切なヘッダーを送信することにより、 アクセスを明示的に許可できます。上記のリクエストに対しては、次のようになります:
HTTP/1.1 200 OK ... Access-Control-Allow-Origin: https://public.example.com Access-Control-Allow-Methods: GET Access-Control-Allow-Credentials: true Access-Control-Allow-Private-Network: true Content-Length: 0 ...
1.2.3. ナビゲーション
https://go/ で内部リンク短縮
サービスを運用しており、
従業員はしばしばそのようなリンクを互いにメールで送信します。メールサーバーは、
従業員がオフィスにいなくても働けるようにするため、パブリック
アドレスでホストされています。なんて配慮でしょう!
https://mail.mega.corp/ から https://go/* リンクをクリックすると、
CORS プリフライトリクエストがトリガーされます。これは、パブリック
アドレスからプライベートアドレスへのリクエストであるためです:
OPTIONS /short-links-are-short-after-shortening HTTP/1.1 Host: go Access-Control-Request-Method: GET Access-Control-Request-Private-Network: true ... Origin: https://mail.mega.corp
従業員が期待通りにそのようなリンクへナビゲートし続けられるようにするため、 MegaCorp はプライベートネットワークリクエストを許可することを選びます:
HTTP/1.1 200 OK ... Access-Control-Allow-Origin: https://mail.mega.corp Access-Control-Allow-Methods: GET Access-Control-Allow-Credentials: true Access-Control-Allow-Private-Network: true Content-Length: 0 ...
しかし、MegaCorp の漏えい防止部門は、このアクセスにより外部の人々が
短縮サービスが返すリダイレクト先の場所を読めるようになるのではないかと懸念しています。
彼らは https://go/shortlink が漏えいすることにはおおむね諦めていますが、
ターゲット
(https://sekrits/super-sekrit-project-with-super-sekrit-partner) まで漏えいしたら、
本当に悲しむでしょう。
MegaCorp の短縮リンクエンジニアは、プリフライトに対してのみ CORS ヘッダーを返すことで、 この潜在的な失敗を慎重に回避します。「実際の」 ナビゲーションは CORS ヘッダーを必要とせず、彼らは実際には クロスオリジンリクエストを CORS 同一オリジンとしてサポートしたいわけではありません:
// Request: GET /short-links-are-short-after-shortening HTTP/1.1 Host: go ... // Response: HTTP/1.1 301 Moved Permanently ... Location: https://sekrits/super-sekrit-project-with-super-sekrit-partner
ナビゲーションは通常通り進行しますが、mail.mega.corp はレスポンスと
CORS 同一オリジンとは見なされません。
1.2.4. 混在コンテンツ
パブリックインターネット上の潜在的に信頼できるオリジンを持つ Web サイトが デバイスからデータをリクエストすると、ユーザーエージェントはリクエスト元を パブリック、デバイスをプライベート(潜在的に信頼できるオリジンではない)として認識します。 これにより、CORS プリフライトリクエストと、(正しいプリフライトレスポンスを受け取った後の) ユーザーへの許可プロンプトの両方がトリガーされます。
Web サイトは、fetch() API
オプションとして IPAddressSpace
を明示的に主張する必要があります:
fetch( "http://router.local/ping" , { targetAddressSpace: "private" , });
デバイスは、プリフライトレスポンスヘッダーで許可を明示的に示し、 一意のデバイス ID とユーザーフレンドリーなデバイス名を提供することで、アクセスを許可できます。 上記のリクエストへのレスポンス例:
HTTP/1.1 200 OK ... Access-Control-Allow-Origin: https://mail.mega.corp Access-Control-Allow-Methods: GET Access-Control-Allow-Credentials: true Access-Control-Allow-Private-Network: true Private-Network-Access-ID: 01:23:45:67:89:0A Private-Network-Access-Name: userA’s MegaCorp device Content-Length: 0 ...
許可プロンプトが表示され、デバイスヘッダーからの ID と名前が表示されます。 ユーザーが許可すると、リクエストは続行されます。
2. フレームワーク
2.1. IP アドレス空間
IPAddressSpace
を次のように定義します:
enum {IPAddressSpace ,"public" ,"private" };"local"
すべての IP アドレスは IP アドレス空間に属し、これは次の 3 つの異なる値のいずれかです:
-
ローカル: ローカル ホストのみを含みます。言い換えると、ターゲットがデバイスごとに異なる アドレスです。
-
プライベート: 現在のネットワーク内でのみ意味を持つ アドレスを含みます。言い換えると、ターゲットがネットワーク上の位置に応じて異なるアドレスです。
-
パブリック: その他すべての アドレスを含みます。言い換えると、IP ネットワーク上のすべてのデバイスで ターゲットがグローバルに同じであるアドレスです。
便宜上、さらに次の用語を定義します:
IP アドレス空間 lhs は、次の条件のいずれかが真である場合、 IP アドレス空間 rhs より よりパブリックでないものとします:
IP アドレス address のIP アドレス空間を決定するには、次の手順を実行します:
-
address が
::ffff:0:0/96"IPv4-mapped Address" アドレスブロックに属する場合、address をそれに埋め込まれた IPv4 アドレスで置き換えます。 -
非パブリック IP アドレス ブロック" 表の各 row について:
-
address が row のアドレスブロックに属する場合、row の アドレス空間を返します。
-
-
パブリックを返します。
| アドレスブロック | 名前 | 参照 | アドレス空間 |
|---|---|---|---|
127.0.0.0/8
| IPv4 ループバック | [RFC1122] | ローカル |
10.0.0.0/8
| プライベート利用 | [RFC1918] | プライベート |
100.64.0.0/10
| キャリアグレード NAT | [RFC6598] | プライベート |
172.16.0.0/12
| プライベート利用 | [RFC1918] | プライベート |
192.168.0.0/16
| プライベート利用 | [RFC1918] | プライベート |
198.18.0.0/15
| ベンチマーク | [RFC2544] | ローカル |
169.254.0.0/16
| リンクローカル | [RFC3927] | プライベート |
::1/128
| IPv6 ループバック | [RFC4291] | ローカル |
fc00::/7
| ユニークローカル | [RFC4193] | プライベート |
fe80::/10
| リンクローカルユニキャスト | [RFC4291] | プライベート |
::ffff:0:0/96
| IPv4 マップ | [RFC4291] | マップ先 IPv4 アドレスを参照 |
ユーザーエージェントは、特定の IP アドレスブロックのアドレス空間が、 管理者またはユーザーの設定を通じて上書きされることを許可してもよいです。これは、たとえば、 上記のアルゴリズムではほとんどの IP アドレスがパブリックと見なされる IPv6 イントラネットを保護するために、代わりに ユーザーエージェントがイントラネットをプライベートとして扱うよう設定する場合に有用です。
Note: 169.254.0.0/16 などのリンクローカル IP
アドレスは、
ネットワークリンク上のすべてのデバイスで同じターゲットを識別できるため、
プライベートと見なされます。この仕様の以前のバージョンでは、
代わりにそれらをローカルと見なしていました。
Note: リンクローカル IP アドレスは、リンクを越えて共有されると意味を失います。 これは本質的には非パブリック IP アドレスと異なりません。それらはいずれも、 それを越えると曖昧になる何らかの局所性を持ちますが、 混乱した代理問題の特有のリスクを提示します。 [LINK-LOCAL-URI] は、URI 内のリンクローカル IP アドレスの構文を定義することで、この問題を解決しようとしています。
Note: 各IP アドレス空間の内容は、ある時点では
IANA Special-Purpose Address Registries
([IPV4-REGISTRY] および [IPV6-REGISTRY]) と、そこで定義された
Globally Reachable ビットに従って決定されていました。
これは、spec issue #50 で説明されているように、
私たちの用途には不正確なシグナルであることが判明しました。
これらのアドレスへのアクセスが完全にブロックされたら、 IPv4 マップ IPv6 アドレスの特別扱いを削除します。 [Issue #36]
2.2. プライベートネットワークリクエスト
request (request) は、
request のcurrent url の host
が、request のpolicy container のIP アドレス
空間よりもよりパブリックでないIP アドレス
空間を持つ IP アドレスに対応付けられる場合、
プライベートネットワークリクエストです。
IP アドレスを 3 つの大まかなアドレス空間に分類することは、 不完全で理論的に健全ではないアプローチです。これは、2 つのネットワークエンドポイントが自由に通信することを 許可されるべきかどうか、言い換えると、エンドポイント A が エンドポイント C 上のユーザーエージェントを経由してピボットすることなくエンドポイント B から到達可能かどうかを 判断するために使われる代理です。
このアプローチにはいくつかの欠点があります:
-
偽陽性: パブリックアドレスを持つイントラネットサーバーは、 同じイントラネット上のプライベート アドレスを持つ別のサーバーへ直接リクエストを発行できない可能性があります。
-
偽陰性: 家庭ネットワークと VPN など、2 つの異なるプライベート ネットワークに接続されたクライアントは、VPN から配信された Web サイトが家庭ネットワーク上の デバイスにアクセスすることを許してしまう可能性があります。下記の課題も参照してください。
それでも、この仕様は、ネットワーク構成がそれほど複雑でない Web のほとんどのユーザーに 広く影響するセキュリティ問題に対して、実用的な解決策を提供することを目指しています。
プライベート
ネットワークリクエストの定義は、current url の host
がIP アドレス空間がパブリックではない IP アドレスに対応付けられる
すべてのクロスオリジンリクエストを対象に拡張できます。これにより、
プライベートネットワーク上の悪意あるサーバーが他のサーバーを攻撃することを防げます。
そのような変更を出荷するための労力は、現時点では見返りに値しないと判断されています。
これは後で段階的な改善として出荷できます。 [Issue #39]
NOTE: 一部のプライベートネットワークリクエストは、 他のものよりも保護が難しいです。詳細については § 4.4 ロールアウトの困難を参照してください。
2.3. 追加の CORS ヘッダー
Access-Control-Request-Private-Network
は、そのrequestがプライベートネットワークリクエストであることを示します。
Access-Control-Allow-Private-Network
は、リソースが外部ネットワークと安全に共有可能であることを示します。
Note: これらのヘッダーは一時的に
Access-Control-Request-Local-Network および Access-Control-Allow-Local-Network
として規定されていましたが、
互換性への影響のため、この決定は取り消されました。詳細については issue
#91 を参照してください。
Private-Network-Access-Name
は、プライベートネットワークアクセス許可プロンプトで、ユーザーに人間に分かりやすい名前を
提供しようとします。
Private-Network-Access-ID ヘッダーは、
PrivateNetworkAccessPermissionDescriptor
内で、IP アドレスをまたいで同一の
デバイスを識別するために使用されます。
2.4. treat-as-public-address Content Security Policy ディレクティブ
treat-as-public-address ディレクティブは、ユーザーエージェントに対し、 文書が実際にはプライベートアドレスまたはローカルアドレスから配信されていたとしても、 それをパブリックアドレスから配信されたかのように扱うよう指示します。 すなわち、非パブリック文書が、プリフライトなしで他の非パブリック文書に接続する権限を 放棄するための機構です。
このディレクティブの構文は、次の ABNF 文法で説明されます:
directive-name = "treat-as-public-address" directive-value = ""
このディレクティブには報告要件はありません。Content-Security-Policy-Report-Only ヘッダー内、
または
meta
要素内で配信された場合、完全に無視されます。
このディレクティブの初期化アルゴリズムは次のとおりです。
環境設定オブジェクト (context)、
Response (response)、および
policy (policy) が与えられたとき:
-
policy のdisposition が "
enforce" である場合、 context のpolicy containerのIP アドレス空間をパブリックに設定します。
2.5. 機能検出
この仕様の以前のバージョンでは、addressSpace enum プロパティを
Document
および WorkerGlobalScope
に追加することを提案していましたが、
フィンガープリンティングの懸念により削除されました(issue
#21 を参照)。
文書は、UA がこの仕様を実装しているかどうかに基づいて異なる振る舞いをすべきではありません。 すべての文書は、UA が実装していると仮定すべきです。
2.6. 許可プロンプト
[Issue#23] での議論を受けて、 混在コンテンツ検査を緩和するためにプライベートネットワークアクセス許可プロンプトが導入されます。
この許可の目標は、パブリック Web サイトから HTTP 経由でローカルネットワークサーバーへ 通信できるようにすることです。これは、そうでなければセキュアコンテキスト制限および混在 コンテンツ検査により防止されます。プライベートネットワークサーバーを HTTPS に移行することは、実際にはしばしば困難で、 時には不可能ですらあることが分かっています。
fetch() オプションバッグに新しいパラメーターが追加されます:
fetch( "http://router.local/ping" , { targetAddressSpace: "private" , });
これは、スキームが非セキュアであってもフェッチを許可し、ターゲットサーバーへの接続を取得するよう
ブラウザーに指示します。新しい fetch() API は後方互換です。
この機能は、混在コンテンツ一般を迂回するために悪用できないことに注意してください。
リモート IP アドレスが targetAddressSpace オプション値で指定された IP アドレス空間に
属さない場合、リクエストは失敗します。属する場合、CORS プリフライトリクエストが送信されます。
ターゲットサーバーはその後、次の 2 つのヘッダーで拡張された CORS プリフライトレスポンスで応答します:
Private-Network-Access-Name: <some human-readable device self-identification> Private-Network-Access-ID: <some unique and stable machine-readable ID, such as a MAC address>
例:
Private-Network-Access-Name: "My Smart Toothbrush" Private-Network-Access-ID: "01:23:45:67:89:0A"
Private-Network-Access-ID
は、コロンで区切られた 6 個の
16 進バイトとして表現される 48 ビット値であるべきです。Private-Network-Access-Name
は、[ECMAScript] regexp /^[a-z0-9_-.]+$/ に一致する文字列である有効な名前であるべきです。
名前の UTF-8 符号単位の最大数は 248 です。
その後、ターゲットデバイスへのアクセス許可を求めるプロンプトがユーザーに表示されます。
-Name ヘッダーは、オリジン(多くの場合、生の IP リテラル)の代わり、またはそれに加えて、
ユーザーに親しみやすい文字列を提示するために使用されます。-ID ヘッダーは、
許可をキー化し、IP アドレスをまたいでデバイスを認識するために使用されます。
実際、DHCP の広範な利用により、デバイスは定期的に IP アドレスを変更する可能性が高く、
私たちはデバイス間の混同と許可疲れの両方を避けたいと考えています。
ユーザーが許可を与えることを決めた場合、フェッチは続行されます。そうでない場合、 失敗します。その後、許可は永続化されます。同じイニシエーターオリジンに属し、 同じサーバー(生の IP アドレスを使用する場合は異なるオリジンである可能性もある)へアクセスする意図を宣言する 次の文書は、許可プロンプトをトリガーしません。最初の CORS プリフライトレスポンスが同じ ID を運び、 ブラウザーはその文書がすでにそのサーバーへアクセスする許可を持っていることを認識します。
既存の -Name または -ID がない場合、プロンプトは IP アドレスのみで表示されます。
ユーザーが許可を与えることを決めた場合、フェッチは続行されます。
許可は一時的な許可として保存され、現在のウィンドウプロセスの間だけ永続します。
3. 統合
この節は規範的ではありません。
この文書は、上記の例で概説された軽減策を実装するために、 他の仕様に対するいくつかの変更を提案します。これらの 統合は明確さのためにここで概説されていますが、外部文書が 規範的参照です。
3.1. セキュアコンテキスト制限
UA は、プライベートサーバーがプリフライトを介してそのような リクエストにオプトインするとしても、非セキュアなパブリックコンテキストがプライベートアドレスからリソースをリクエストすることを 許可してはなりません。 プライベートリソースへのリクエストは、 リクエストを開始するクライアントの完全性を確保することで軽減されるリスクを提示します。特に、ネットワーク 攻撃者が、非セキュアオリジンに対するエンドポイントの同意を 容易に悪用できるべきではありません。
混在コンテンツ検査 [MIXED-CONTENT-2] は、セキュアコンテキストが HTTP 経由でリクエストを行うことを防ぐため、この制限はプライベート ネットワークサーバーが HTTPS に移行することを要求するように見えます。これはしばしば困難から不可能です。 ユーザーの同意があれば、セキュアコンテキストが HTTP 経由でプライベートネットワークに リクエストを行うことを可能にするため、新しい許可プロンプトが導入されます。
3.2. Permissions との統合
この文書は、強力な機能を定義します。これは name "private-network-access" により識別されます。これは
次の型を上書きします:
- permission descriptor type
-
permission descriptor type の
"private-network-access"機能は、 デフォルトの permission descriptor type から 継承する次の WebIDL インターフェイスにより定義されます:dictionary :PrivateNetworkAccessPermissionDescriptor PermissionDescriptor {DOMString ; };id
3.3. Mixed Content との統合
Should fetching request be blocked as mixed content? は、 許可される条件の 1 つに 次の条件を追加するよう修正されます:-
request のoriginが潜在的に信頼できるオリジンではなく、 かつ request のターゲット IP アドレス空間がプライベートまたはローカルであること。
3.4. Fetch との統合
この文書は Fetch に対するいくつかの変更を提案しており、その含意は次のとおりです: プライベートネットワークリクエストは、 そのclientが セキュアコンテキストであり、かつターゲットオリジンへの CORS プリフライトリクエストが成功した場合にのみ 許可されます。リクエストが混在コンテンツとしてブロックされるはずであった場合でも、 Web サイトがプライベート ネットワークへアクセスする意図を示し、ユーザーが許可を与える限り許可できます。
Note: これにはナビゲーションも含まれます。これらは実際、 サブリソースリクエストほど巧妙ではないものの、CSRF 攻撃をトリガーするために使用できます。
Note: [FETCH] はまだ DNS 解決の詳細をFetch アルゴリズムに統合していませんが、この仕様には十分な 接続を取得するアルゴリズムを定義しています。Private Network Access 検査は、新たに取得された接続に適用されます。Happy Eyeballs ([RFC6555], [RFC8305]) などの複雑性を考えると、 これらの検査は、 複数の IP アドレスを持ち、 IP アドレス 空間境界をまたぐホストに対して、非決定的に成功または失敗する可能性があります。
3.4.1. CORS プリフライト
HTTP fetch アルゴリズムは、セキュアコンテキストから開始されたすべての プライベートネットワークリクエストに対してプリフライトが トリガーされることを確実にするよう調整されるべきです。
ここでの主な問題は、レスポンスのIP アドレス空間が、 HTTP-network fetch で接続が取得されるまで分からないことです。これは CORS-preflight fetch の下にレイヤー化されています。
3.4.2. フェッチ
以下は潜在的な解決策のスケッチです:
-
Connection オブジェクトに、新しい IP アドレス空間プロパティが与えられ、初期値は null です。これは WebSocket 接続にも適用されます。
-
接続を取得するアルゴリズムにおいて、 connection をユーザーエージェントの接続プールに追加する直前に、新しい手順が追加されます:
-
connection のIP アドレス空間を、 connection のリモートエンドポイントの IP アドレスに対して IP アドレス空間を決定する アルゴリズムを実行した結果に設定します。
リモートエンドポイントの概念はまだ [FETCH] で規定されていないため、 これはある程度まだ手振りです。 [Issue #33]
-
-
Request オブジェクトに、新しい ターゲット IP アドレス空間プロパティが与えられ、初期値は null です。
-
Response オブジェクトに、新しい IP アドレス空間プロパティが与えられ、その値は IP アドレス 空間であり、初期値は null です。
-
新しい Private Network Access check アルゴリズムを定義します。 request request と connection connection が与えられたとき:
-
request のoriginが潜在的に信頼できる オリジンであり、かつ request のcurrent URLのoriginが request のoriginと同一オリジンである場合、 null を返します。
-
request のpolicy containerが null である場合、 null を返します。
NOTE: request のpolicy containerが null である場合、 PNA 検査は request に適用されません。fetch アルゴリズムの利用者は、 request のclientを、非 null の policy containerを持つ 環境設定オブジェクトに設定し、 fetch が request のpolicy containerをそれに従って初期化するようにするか、 または request のpolicy containerを非 null 値に直接設定するよう注意するべきです。
-
request のターゲット IP アドレス空間が null でない場合:
-
Assert: request のターゲット IP アドレス空間は パブリックではありません。
-
connection のIP アドレス空間が request のターゲット IP アドレス空間と等しくない場合、 network errorを返します。
-
null を返します。
-
-
connection のIP アドレス空間が、request の policy containerのIP アドレス空間よりよりパブリックでない場合:
-
error をnetwork errorとします。
-
request のclientがセキュアコンテキストではない(null の場合を含む)場合、 error を返します。
-
error を返します。
-
-
null を返します。
-
-
fetch アルゴリズムは、request のpolicy containerが設定された直後に 次の手順を追加するよう修正されます:
-
request のターゲット IP アドレス空間がパブリックである場合、network errorを返します。
-
-
HTTP-network fetch アルゴリズムは、 新たに取得された connection が failure でないことを確認した直後に、 3 つの新しい手順を追加するよう修正されます:
-
privateNetworkAccessCheckResult を、 fetchParams のrequestおよび connection について Private Network Access checkを実行した結果とします。
-
privateNetworkAccessCheckResult がnetwork errorである場合、 privateNetworkAccessCheckResult を返します。
-
request request と boolean makeCORSPreflight が与えられたとき、プリフライトモードを決定する新しいアルゴリズムを定義します:
-
makeCORSPreflight が true で、次の条件のいずれかが true である場合:
-
request を使用して、request のmethodに対する method cache entry match が存在せず、 かつ request のmethodがCORS セーフリスト化メソッドではないか、 または request のuse-CORS-preflight flagが設定されている場合。
-
request のheader listを使用して、 CORS-unsafe request-header names 内に、 request を使用した header-name cache entry match が存在しない項目が少なくとも 1 つ存在する場合。
その場合:
-
request のターゲット IP アドレス空間が null でない場合、"cors+pna" を返します。
-
それ以外の場合、"cors" を返します。
-
-
request のターゲット IP アドレス空間が null でない場合、 "pna" を返します。
-
それ以外の場合、"none" を返します。
-
-
HTTP fetch 内で、サービスワーカーを介してフェッチを処理した後に response がまだ null である場合に実行される既存の手順に基づいて、 HTTP-no-service-worker fetch という新しいアルゴリズムを定義し、 それらを次のように少し修正します:
-
preflightMode を、request および makeCORSPreflight を与えて プリフライトモードを 決定するを呼び出した結果とします。
-
条件全体 "If makeCORSPreflight is true and ..., Then:" を 次のように置き換えます:
-
preflightMode が "none" でない場合:
-
-
"request を与えてCORS-preflight fetchを実行する" を、 "request および preflightMode を与えて CORS-preflight fetchを実行する" に置き換えます。
-
CORS-preflight fetchを実行した直後:
-
preflightResponse がnetwork errorである場合:
-
preflightResponse のIP アドレス空間が null である場合、preflightResponse を返します。
-
request のターゲット IP アドレス 空間を preflightResponse のIP アドレス空間に設定します。
-
fetchParams を与えてHTTP-no-service-worker fetchを実行した結果を返します。
-
-
-
HTTP-network-or-cache fetchを実行した直後:
-
response がnetwork errorであり、 response のIP アドレス空間が非 null である場合:
-
request のターゲット IP アドレス 空間を preflightResponse のIP アドレス空間に設定します。
-
fetchParams を与えてHTTP-no-service-worker fetchを実行した結果を返します。
-
-
Note: 再帰時には request のターゲット IP アドレス空間が 非 null 値に設定されるため、この再帰は最大 1 レベルの深さまでしか到達しません。
-
-
CORS-preflight fetch アルゴリズムは、 新しいパラメーター preflightMode(デフォルト "cors")を取り、 新しいヘッダーを次のように処理するよう調整されます:
-
preflightMode が true である場合にのみ、`
Accept` および `Access-Control-Request-Headers` を preflight のheader listに付加します。 -
HTTP-network-or-cache fetchを実行する直前:
-
request のターゲット IP アドレス空間 が null でない場合:
-
preflight のheader list内で "
Access-Control-Request-Private-Network" を "true" に設定します。
-
-
-
CORS check の直後:
-
preflightMode が "pna" または "cors+pna" である場合、
-
Assert: request のターゲット IP アドレス 空間は null ではありません。
-
allow を、 "
Access-Control-Allow-Private-Network" および response のheader listを与えて header list values を抽出する結果とします。 -
allow が "
true" でない場合、network errorを返します。 -
requestWithoutTargetIpAddressSpace を request のコピーとしますが、そのターゲット IP アドレス 空間を null に設定します。
-
should fetching requestWithoutTargetIpAddressSpace be blocked as mixed content が allowed を返す場合、 null を返します。
-
Private-Network-Access-IDまたはPrivate-Network-Access-Nameが null または 空である場合、targetId を request のターゲット IP アドレス空間とします。許可を一時的な 許可として保存し、その後 null を返します。 -
targetId を、 "
Private-Network-Access-ID" および response のheader listを与えて header list values を抽出する結果とします。 -
targetId がコロンで区切られた 6 個の 16 進バイトの文字列ではない場合、 network errorを返します。
-
targetName を、 "
Private-Network-Access-Name" および response のheader listを与えて header list values を抽出する結果とします。 -
targetName が [ECMAScript] regexp /^[a-z0-9_-.]+$/ に一致しない、または 248 UTF-8 符号単位を超える場合、 network errorを返します。
-
state を、次の記述子を使用して使用する許可を 要求する結果とします:
{ name: "private-network-access" , id: targetId, } -
state が
"denied"である場合、network errorを返します。 -
null を返します。
-
-
-
-
最後に、DNS リバインディング攻撃の影響を軽減するため(§ 5.3 DNS リバインディングを参照)、CORS-preflight cache はIP アドレス 空間情報を考慮するよう調整されます:
-
新しい IP アドレス空間プロパティ (null またはIP アドレス空間)が各cache entryに追加されます。
-
この新しいプロパティは、新しい cache entry を作成するアルゴリズムにより、 request のターゲット IP アドレス空間から初期化されます。
-
この新しいプロパティは、cache entry match アルゴリズムにより検査されます:
-
entry のIP アドレス空間が request のターゲット IP アドレス空間と 等しいこと。
-
-
3.4.3. Fetch API
Fetch API も調整する必要があります。
-
RequestInfoに任意のエントリを付加します。そのキーは targetAddressSpace であり、値はIPAddressSpaceです。partial dictionary RequestInit {IPAddressSpace ; };targetAddressSpace -
request 内で上記を表す新しい {=targetAddressSpace=} を定義します。
partial interface Request {readonly attribute IPAddressSpace ; };targetAddressSpace -
new Request(input, init)は、 this のrequestを request に設定する直前に、 次の手順を付加されます:-
init["
targetAddressSpace"] が存在する場合、 init["targetAddressSpace"] に基づいて switch します:- public
- 何もしません。
- private
- request のターゲット IP アドレス空間をプライベートに設定します。
- local
- request のターゲット IP アドレス空間をローカルに設定します。
-
3.4.4. 禁止ヘッダー名
禁止リクエストヘッダー名のリストに、新しいエントリ
Access-Control-Request-Private-Network
が追加されます。
ユーザーエージェントは、他の CORS ヘッダーと同様に、このヘッダーを完全に制御するべきです。
3.5. WebSockets との統合
WebSocket
ハンドシェイクは <img> タグとおおよそ同じ能力を持つため、WebSocket
ハンドシェイクに先立ってプリフライトリクエストを送信するべきでしょう。
WebSocket 接続を確立するはFetch
アルゴリズムに依存しているため、
これを規定するための追加作業は不要かもしれません。 [Issue
#14]
この仕様の以前のバージョンでは、単に新しい ヘッダー(§ 2.3 追加の CORS ヘッダーを参照)を WebSocket ハンドシェイクに追加することを 提案していました。しかし、これは CSRF 攻撃から完全に守るには 十分ではありません。
3.6. HTML との統合
[FETCH] での検査をサポートするため、ユーザーエージェントは、 ネットワークリクエストが行われるコンテキストのソースIP アドレス空間を記憶しなければなりません。この 目的のため、[HTML] 仕様は次のようにパッチされます:
-
新しい IP アドレス空間プロパティが policy container 構造体に追加されます。
-
初期値はパブリックです。
-
-
policy container を複製するアルゴリズムに追加の手順が追加されます:
-
fetch response から policy container を作成するアルゴリズムに追加の手順が追加されます:
example.com が
パブリックアドレス(たとえば
123.123.123.123)に解決されると仮定すると、
https://example.com/document.html へナビゲートするときに作成される Document
は、そのpolicy containerのIP アドレス
空間プロパティがパブリックに設定されます。
この Document
がその後 about:srcdoc iframe を埋め込む場合、子
フレームの Document
は、そのpolicy containerのIP
アドレス空間プロパティがパブリックに設定されます。
一方、example.com がローカルアドレス(たとえば
127.0.0.1)に解決される場合、
https://example.com/document.html へナビゲートするときに作成される Document
は、そのpolicy containerのIP
アドレス空間プロパティがローカルに設定されます。
3.7. ワーカー
この節は規範的ではありません。
WorkerGlobalScope
はすでに、policy containerフィールドを持ち、これは
fetch response から
policy container を作成するアルゴリズムを使用して設定されるため、Fetch および HTML との上記の統合は、
文書と同様にワーカーコンテキストにも適用されます。
example.com が
パブリックアドレス(たとえば
123.123.123.123)に解決されると仮定すると、
https://example.com/worker.js からスクリプトをフェッチすることにより作成される
WorkerGlobalScope
は、そのpolicy containerのIP アドレス空間プロパティがパブリックに設定されます。
このワーカーにより開始される任意の fetch requestが、接続を取得することにより、プライベートまたはローカルのアドレス空間内の IP アドレスに接続する場合、それはプライベートネットワークリクエストになります。
Service Worker のsoft update
アルゴリズムは、更新されたスクリプトをfetchingするときに、残念ながら
request client を "null" に設定します。
これはあらゆる種類の問題を引き起こし、上記で示した
private network access check アルゴリズムに干渉します。
実際、fetch 中に
policy containerをコピーする元となる
request clientがありません。 [Issue #83]
4. 実装上の考慮事項
4.1. file URL はどこに位置づけられるか?
file URL が上で概説したパブリック/プライベート方式の中で
どのように位置づけられるかは、完全には明確ではありません。一方では、悪意ある HTML
ファイルをローカルで開くことで人々が自分自身を害するのを防げるとよいでしょうが、他方では、ローカルで
実行されるコードは、一貫した脅威モデルの外側に多少あります。
当面は、file URL をローカルとして扱う側に倒すことにしましょう。
それらはループバックアドレス上の他のものと同じくらい、ローカル
システムの一部であるように見えるためです。
4.2. プロキシ
Chromium におけるこの仕様の現在の実装では、プロキシは それらがプロキシするリソースのアドレス 空間に影響します。具体的には、 プロキシを介してフェッチされたリソースは、プロキシ自身の IP アドレスからフェッチされたものと 見なされます。
foo.example により配信される Document
が、
プライベートアドレス上のプロキシを介して
ユーザーエージェントによりフェッチされた場合、その Document
の
policy container のIP
アドレス空間はプライベートに設定されます。
その Document
は次に、ブラウザーがアクセス可能な他のプライベートアドレスへリクエストを行うことを許可されます。
これにより、Web サイトはプライベートアドレスへのリクエストが許可されていることを観察することで、 自身がプロキシされたことを学習できる可能性があり、これはプライバシー 情報漏えいです。これにはプライベートネットワーク上のリソースの URL を正しく推測する必要がありますが、 1 回の正しい推測で十分です。
これは比較的まれであり、追加の軽減策を正当化するほどではないと予想されます。結局のところ、 現状ではすべての Web サイトがすべての IP アドレスへ 何の制限もなくリクエストを行えます。
プロキシが「どうかこのリソースをとにかくパブリック/プライベートとして扱ってください」と ブラウザーに伝え、それによりプロキシの背後にある IP アドレスに関する情報を 引き継ぐ機構を探ることは興味深いでしょう。これは上で議論した CSP ディレクティブに、若干の 修正を加えた形を取るかもしれません。
4.3. HTTP キャッシュ
Chromium におけるこの仕様の現在の実装は、どの種類のリソースが キャッシュから読み込まれるかに応じて、HTTP キャッシュと 2 つの注目すべき方法で 相互作用します。
4.3.1. 主リソース
キャッシュされたレスポンスから構築された文書は、 そのレスポンスが最初に読み込まれた IP アドレスを記憶します。そのIP アドレス空間は、その IP アドレスから新たに導出されます。
一般的な場合、これは文書の policy containerのIP アドレス空間が 変更されずに復元されることを意味します。ただし、ユーザーエージェントの設定が 変更された場合には、導出されたIP アドレス空間が異なる可能性があります。
http://foo.example/ へナビゲートし、主リソースを
1.2.3.4 から読み込み、キャッシュし、その後結果の文書の
policy containerのIP
アドレス空間をパブリックに設定します。
その後、ユーザーエージェントが再起動され、1.2.3.4 を代わりに
プライベートアドレスとして分類すべきであると指定する新しい設定が適用されます。
ユーザーエージェントは再び http://foo.example/ へナビゲートし、
主リソースを HTTP キャッシュから読み込みます。結果の文書のpolicy containerのIP
アドレス空間は、
今度はプライベートに設定されます。
4.3.2. サブリソース
HTTP キャッシュから読み込まれるサブリソースは、Private Network Access check の対象です。これは上記のアルゴリズムにはまだ反映されていません。 というのも、その検査はHTTP-network fetch でのみ適用されるためです。
ここでの Chromium の振る舞いを規定し説明します。 [Issue #75]
セキュリティ上の含意についての議論は、§ 5.6 HTTP キャッシュを参照してください。
4.4. ロールアウトの困難
Private Network Access は本質的に、プライベート ネットワークへの直接アクセスを非推奨にし、より安全なユーザーエージェント介在の代替手段を優先します。Web の非推奨化は困難です。Chromium は、この仕様の一部を出荷するまでに 多くの障害に遭遇しました。
特に、非セキュアコンテキストからプライベート IP アドレス空間内のフェッチを ローカル IP アドレス空間へ制限することを出荷するのは、見返りが小さいため特に 困難であることが分かりました。実際、そのようなフェッチを悪用するには、 攻撃者がすでにプライベートネットワーク内に足場を持っている必要があり、これは 攻撃の難度を大幅に上げます。その結果、Chromium はこれらの フェッチを一時的に制限から除外し、パブリック IP アドレス空間からのフェッチに 注力することを選びました。
5. セキュリティとプライバシーの考慮事項
5.1. ユーザー介在
この文書の提案は、デバイスがパブリックインターネットからのアクセスに同意することのみを 保証します。ユーザーエージェントは、そのようなアクセスを拒否することがユーザーの利益になる可能性があるため、 デバイス自体が許可する場合であっても、ユーザーもそのようなアクセスに同意することを 確保してもよいです。
この介在は、明示的な許可付与、PAKE のような何らかのペアリング儀式、またはユーザーエージェントが考案しうる その他の巧妙なインターフェイスを通じて行うことができます。
5.2. 混在コンテンツ
この文書の提案により追加される CORS 制限は、混在コンテンツ検査 [MIXED-CONTENT-2] を不要にしません。CORS プリフライトリクエストを通じて得られるデバイスの同意は必要ですが、十分ではありません。
Note: [MIXED-CONTENT-2] は、
セキュアコンテキストが、localhost または
127.0.0.0/8 もしくは ::1/128 ブロック内の IP
アドレスをhostとするオリジンからリソースをフェッチすることを防ぎません。
潜在的に信頼できるオリジンの定義も参照してください。
パブリックページから、(上記の 例外以外のホスト上の)プライベートまたはローカルリソースをフェッチしたい開発者は、 接続がセキュアであることを確保しなければなりません。これには、[PLEX] のような解決策が関与するかもしれません。そこでは Web PKI 証明書がユーザー固有のドメイン名に発行され、それらが ユーザーのプライベートネットワーク上でのみ意味を持つプライベート IP アドレスに解決されます。
一部の消費者向けルーターは、 非パブリック IP アドレスに解決される DNS レスポンスを単にブロックすることで、DNS リバインディング攻撃に対して過度に攻撃的な保護を実装しています。これは [PLEX] のような解決策の障害になります。回避策は リンク先の課題で議論されています。 [Issue #23]
この問題領域はすでに何度か検討されており、どこかの時点で 再検討する価値があるように思えます。上で示唆したようなペアリング儀式、または [SECURE-LOCAL-COMMUNICATION] で浮上したアイデアの 1 つを想像できます。
5.3. DNS リバインディング
ここで説明される軽減策は、特定のリソースを読み込むときにユーザー エージェントが実際に接続する IP アドレスに対して動作します。この検査は、 新しい接続が行われるたびに実行されなければなりません。さもなければ、DNS リバインディング攻撃が ユーザーエージェントをだまして、本来明らかにすべきでない情報を明らかにさせる可能性があります。
CORS-preflight cache への変更は、 この攻撃ベクトルを軽減することを意図しています。
5.4. 軽減の範囲
この文書の提案は、プライベート Web サービスへの攻撃を軽減するだけであり、完全に解決するものではありません。たとえば、ルーターの Web ベース管理インターフェイスは、自身で CSRF に対して防御するよう設計および実装されなければならず、 この文書で規定されるように振る舞う UA に依存するべきではありません。この文書が規定する軽減策は、 今日のプライベート Web サービス実装品質の現実を考えると必要ですが、すべての UA が この軽減策を実装したとしても、ベンダーは自身の責任が免除されたと考えるべきではありません。
5.5. クロスネットワーク混同
ほとんどのプライベートネットワークは互いに通信できませんが、この仕様ではそれらはすべて プライベート IP アドレス空間に属するものとして 扱われます。さらに、プライベート アドレスは、それが使われるプライベートネットワーク上でのみ意味を持ちます。 同じ IP アドレスが、2 つの異なるネットワーク内のまったく異なるデバイスを指す可能性があります。
これはクロスネットワーク攻撃への扉を開きます:
-
ユーザーが 2 つの異なるプライベートネットワーク、すなわち家庭 Wi-Fi ネットワーク と企業 VPN に接続しています。彼らのスマート冷蔵庫がハッキングされています。彼らが スマート冷蔵庫の Web インターフェイスを開くと、それが VPN 経由でアクセス可能な 企業 Web サイトに対して CSRF 攻撃を実行します。
-
ユーザーが、ユーザーにキャプティブポータルページを開いたままにすることを要求する、 悪意あるインターネットカフェの Wi-Fi に接続します。ユーザーはノートパソコンを閉じ、家に帰り、 再びノートパソコンを開きます。キャプティブポータルページ(まだ開いているか、 ユーザーエージェントが以前の状態を復元する際にキャッシュから再読み込みされる)が、 ユーザーの家庭デバイスに対して CSRF 攻撃を実行します。
-
ユーザーが、悪意あるインターネットカフェの Wi-Fi に接続します。そのキャプティブポータル Web サイトは、
http://router.example/popular-library.jsから悪意あるスクリプトを (カフェのネットワーク管理者が悪意ある DNS サーバーを運用している) 非常に長い有効期限でキャッシュします。ユーザーはコンピューターの電源を切り、家に帰り、 再びコンピューターを起動し、http://router.exampleにある ルーターの管理インターフェイスを訪問します。そのインターフェイスは/popular-library.jsを埋め込みます。悪意あるスクリプトが 管理インターフェイスのファーストパーティコンテキストで読み込まれます。
これらの攻撃はいずれも新しいものではありません。この仕様の制限の例にすぎません。
潜在的な軽減策は、 ネットワーク変更を検出し、以前のネットワークに固有の状態を消去することを必要とします。 これを完全に一般的な形で行うことは、すべての状態を消去する以外には不可能である可能性が高いです。 実用的な妥協点に到達できるかもしれません。 [Issue #28]
5.6. HTTP キャッシュ
5.6.1. サブリソースへの検査の適用
以下はもはや 正確ではありません。実装経験により、キャッシュとの統合は CSRF 攻撃からネットワークリソースを保護する場合であっても有用であることが明らかになりました。 この節は書き直す必要があります。 [Issue #75]
キャッシュされたサブリソースは、HTTP キャッシュがソース IP アドレスを記憶しており、 それをPrivate Network Access check アルゴリズムで HTTP-network-or-cache fetch 中に使用できるにもかかわらず、 現在この仕様では保護されていません。
この見かけ上の不一致を修正するのは良い考えかもしれませんが、 この仕様の主な目標、すなわち CSRF 攻撃を防ぐことには直接関係しません。
せいぜい、悪意あるパブリック Web サイトが、ユーザーが過去に特定のプライベート Web サイトを 訪問したかどうかを判断できる可能性がある程度です。このユーザーの プライバシーへの攻撃は、現状より悪くありません。
さらに、HTTP キャッシュのパーティショニングにより、サブリソースは、悪意ある攻撃者が network partition keyを再現できた場合にのみ、キャッシュから読み込めます。 そのcache entryのものです。攻撃者が これを達成する 1 つの方法は、DNS を操作し(§ 5.3 DNS リバインディングも参照)、 最初にキャッシュされたリソースを埋め込んだトップレベルサイトを 偽装することです。
http://router.example へナビゲートし、これは 192.168.1.1 から配信されます。
Web サイトは http://router.example/$BRAND-logo.png からロゴを埋め込み、これはキャッシュされます。
その後、悪意ある攻撃者が router.example を
攻撃者制御のパブリック IP アドレスに再バインドし、何らかの方法でユーザーをだまして
再び http://router.example を訪問させます。悪意ある Web サイトは
ロゴを埋め込もうとし、読み込みが成功するかどうかを監視します。成功した場合、
攻撃者はユーザーのルーターのブランドを特定したことになります。
5.6.2. HTTP キャッシュポイズニング
この仕様は、プライベートネットワークサーバーがパブリック Web サイトから リクエストを受け取ることから保護することを目指していますが、DNS リバインディングは、 認証されていないリソースのキャッシュポイズニングを通じて、類似した攻撃を 実行するために使用できます。
http://router.com を装う攻撃者は、
http://router.com/totally-legit.js に悪意あるスクリプトをキャッシュできます。
後でユーザーが http://router.com/ へナビゲートすると、
ページはポイズニングされたスクリプトをリクエストして実行し、攻撃者のコードが
よりパブリックでない IP アドレス空間で実行される可能性があります。
この攻撃はキャッシュパーティショニングにより部分的に軽減されます。
これにより、攻撃者はリソースをキャッシュする前にトップレベル閲覧コンテキストを
http://router.com/ へナビゲートしなければならなくなり、これは巧妙さを欠きます。
また、これは Private Network Access に固有のものではなく、平文 HTTP に
認証および完全性保護がないことの症状です。
6. IANA に関する考慮事項
Content Security Policy Directive レジストリは、 次のディレクティブおよび参照で更新されるべきです [RFC7762]:
treat-as-public-address-
この文書(§ 2.4 treat-as-public-address Content Security Policy ディレクティブを参照)
7. 謝辞
Ryan Sleevi、Chris Palmer、および Justin Schuh との会話は、この提案の 輪郭を具体化するのに役立ちました。願わくは、彼らがこれをあまり嫌わないことを。 Mathias Karlsson にはラクダの背を折った 最後の藁であるという怪しげな名誉があり、Brian Smith の その結果生じたスレッドへの貢献は、いつものように有用でした。