HTTPヘッダについての調査(3/?)

1回目2回目の続き。前提条件はこれまでと同じ。

代替サービス (Alt-Svc)

ChromeFirefoxは、代替サービス(Alternative Services, RFC7838)のAlt-Svcヘッダをサポートしている。

下が正常なAlt-Svc応答ヘッダの例である。

Alt-Svc: h2="example.jp:443"; ma=864000000; persist=1

ヘッダの意味は下のとおり。

  • h2 ... ALPNのプロトコル
  • example.jp:443 ... 代替サービスのホスト:ポート
  • ma ... maxage=設定を維持する秒数
  • persist ... ネットワーク環境の変更後もこの設定を維持するかのフラグ

ブラウザは30XのHTTPリダイレクト応答でもこのヘッダを解釈する。ブラウザはこの応答ヘッダに出くわすと、それ以降はそのオリジンへの要求パケットの送り先を代替サービスのホスト:ポートに変更する*1

動作のイメージとしては、

  • サーバへの通信を曲げる(DNSのCNAMEや、iptablesを使うのに似ている)。
  • プロトコルのアップグレード。

がセットで行われるような感じである。

Chromeはh2→h3へのアップグレードのみが可能っぽいので、この記事ではFirefoxの挙動について見てみる。

Firefoxが対応しているのは、*→h3に加えて以下のパターンだと思われる。

  • http/1.1 TLShttps) → h2
  • http/1.1 TCP(http) → h2

1つ目はHTTPS(http/1.1)の応答に、2つ目はHTTPの応答にAlt-Svcが付いているパターンだ。それぞれについて見ていくが、

  • 「target」というホストにあるHTTPヘッダインジェクションを使い
  • Alt-Svc: h2="evil:800"のような応答ヘッダを入れる

という前提で話を進める。

http/1.1 TLS (https) → h2

http/1.1 TLSとh2の振り分けは、通常はTLSのハンドシェイク時にALPNにより行うため、Alt-Svcヘッダでh2に上げる必要性は余りないと思われるが、Firefoxはこれを許している(http/1.1 TLSとh2を別のポートで提供するような場合にはこのヘッダを使える)。

ブラウザはAlt-Svc: h2="evil:800"ヘッダを受けると、以降の当該サーバへの接続時には、evil:800にh2で接続する。その際の要求メッセージの中身はAlt-Svcが無い時とほぼ同じである。

Alt-Svcヘッダを出力したホストを「target」とすると、

  • HTTP要求ヘッダのホストやTLSのSNIは「target」である。
  • 要求のCookieヘッダには「target」向けのCookieが含まれる。

同様に応答は、

  • オリジンは「https://target」のままである。
  • リソースのブラウザキャッシュのキーは「https://target」から取得した場合と等しくなる。

つまり、HTTP的な要求/応答の扱いはそのままに、それを送るプロトコルとパケットの送り先だけすり替える形となる。

セキュリティ的に言うと、evil:800はTLSのハンドシェイクの際に(evilではなく)targetのサーバ証明書を提示する必要がある。当然サーバ証明書のチェックはあるため、evil:800は(対象サーバのSSL秘密鍵を持っている時を除き)通信の中身を窃取/改竄できない。

しかし、攻撃者が鍵を持っていなくても、対象のホストにh2に対応したポートがあるならば、単純にそのポートにevil:800からパケットを流す構成にできる。

                                 target                evil

Browser <----------------------- http/1.1 TLS (443)
         Alt-Svc: h2="evil:800"

        ---------------------------------------------> Port forward
                                 h2 (8443) <---------- (evil:800 to target:8443)

つまり、流し込む先のh2のポートさえあれば、攻撃者は物理的に通信経路上にいなくても、Alt-Svcを入れることによって永続的/一時的に中間者の位置に収まることができる。通信内容の窃取/改竄はできなくても、ユーザがいつ対象サービスにアクセスしたかくらいは把握できる。

パケットを流す先の「h2に対応したポート」は別のホストのポートにもなりうる。例えば、ヘッダインジェクションがあるのが「target.example.jp」であり、「sandbox.example.jp」のような別のサブドメインのホスト上でh2が提供されているならば、そちらにパケットを流す手もある。

もちろん別のホストにパケットが流せるのは、

等の条件を満たす時に限られるが、その場合は「target」のリソースの全部/一部*2が「sandbox」から提供される、という妙な状態を作り出せる。「target」側のリソースにはアクセスできなくなるという意味でのDoSになるし、状況によっては他の攻撃につなげられることもあるかもしれない。

さらに、ALPACA的なクロスプロトコル攻撃、つまりTLS上でHTTP以外のアプリケーションを提供するポート(SMTP, POP, FTP等)にパケットを流す攻撃も考えられる。しかし、代替サービスの仕様的には「Alt-Svcで指定されたプロトコル名(h2やh3)のみで代替サービスに接続する」のがMUSTになっている。プロトコル名のチェックがあれば、SMTPサーバ等への接続はSSLのハンドシェイクの段階で失敗する。

ところで、仕組み的に当然であるが、Alt-Svcを入れる攻撃の効果はDNSをいじる攻撃と似ている。実際、DNSをいじれるのであれば、クロスプロトコル攻撃を含め、上に書いた攻撃は成立しうる。ただしAlt-Svcでは、

  • ヘッダを入れる対象はhttp/1.1のみ
  • パケットを流す先はh2のみ

という制約が生じるため、意味のある攻撃が組み立てられる状況はDNSと比べて大きく限定される。

http/1.1 TCP (http) → h2

TCP→h2は「Opportunistic Security for HTTP/2」(RFC8164)を実装している。日本語で言うと日和見暗号であり、通信経路上にアクティブな攻撃者はいない状態で、パッシブな攻撃者によるデータの盗み見を防ぐものだ。現状この仕様を実装しているのはFirefoxのみである。

後述するように、ヘッダインジェクションの文脈では、この仕組み(TCP→h2)を攻撃に使うのは困難だと思われるが、私個人として初めて知った仕組みなのでメモを兼ねて概要を書く。

Firefoxは、TCP接続でAlt-Svc: h2="evil:800"ヘッダに出くわすと、それ以降はアドレスバーのURLを「http://target/」のままとしながら、サーバへの通信はh2(TLS)にし、そのパケットはevil:800に送るようになる。

h2に載る要求メッセージの中身は、TLS→h2の場合と同じく、Alt-Svcが無い時とほぼ変わらない。特筆すべきは、h2(TLS)通信であるにもかかわらず、

  • secure属性を持つCookieはサーバに送られない。
  • SSLのクライアント証明書は提示されない。
  • h2の:schemehttpになる。
  • リソースのブラウザキャッシュのキーは「http://target」から取得した場合と等しくなる(リバースプロキシ等でのキャッシュの扱いには混乱があるかもしれない)。

となる点である。

無条件で代替サービスを指定されてもまずいので、以下のセキュリティ機構がある。

  • 日和見暗号だが、ブラウザはサーバ証明書を検証する。
    代替サービスは「target」のサーバ証明書を提示しなければならない。
  • .well-known URLによるOpt-inがある。
    ブラウザはh2側から所定の.well-knownのJSONを取得して、それが然るべき内容でなければAlt-Svcの指定を無視するようになっている*3

Opt-inがあるのは、「HTTPSのリソースにHTTPのオリジンを与える」かつ「永続的に影響を及ぼす」という、ある意味で危うい仕組みであるためだろう。

これが日和見暗号であるのは、最初のTCPで送られるAlt-Svcヘッダがアクティブな攻撃者により消された場合に、h2への切り替え自体がされなくなるためだ。他のプロトコルでの「STARTTLSが潰されたらダメ」みたいのと同じである。

仕様策定の初期段階では、ヘッダ名はAlt-SvcではなくHTTP-TLSであり、TLSサーバ証明書の検証はoptionalだったようだ。最終的にAlt-Svcと統合され、証明書の検証も必須となった。正規の証明書を用意するくらいならば、大抵の場合はOpportunistic SecurityではなくHTTPSを普通に使えばいいわけで、ユースケースが減ってしまったように見受けられる*4

仕組み的には面白いのだが、ユースケースが殆どなく、対応ブラウザもFirefoxのみであるため、殆ど普及していない機能であると考えられる。したがって.well-knownを置いているホストもまず無いだろうが、上述のようにh2側に.well-knownが無いとそもそも利用できない仕組みである。そのため、HTTPヘッダインジェクションの文脈において、TCP→h2を攻撃に用いるのは困難だろう。


残りの分はまた別途記事にしたい。

*1:仕様的には、代替サービスに接続できない場合はフォールバックして元のサーバに接続することが許されている。

*2:「一部」というのは、例えばAlt-Svcが付いている応答(HTML)は元のサーバから提供され、そこからロードするJSやCSSは代替サービス側から読み込まれる、みたいな状況を指している。

*3:/.well-known/http-opportunistic。Firefoxでは元のTCPとh2の両方で.well-knownチェックを行なう。

*4:ついでに言うと、Firefoxの実装では、http/1.1 TLSのポートは代替サービスに指定できない。指定できるのはh2/h3のみなので使いづらい。