HTTP ホストヘッダーと TLS の SNI について

今回は Web サイトにアクセスする際に利用される FQDN と HTTP のホストヘッダ、TLS の拡張機能の一つである Server Name Indication (SNI) について詳細を記載していきたいと思います。
Azure ですと Application Gateway、Front Door、AppService 等で利用されており、Web サイトを構築する際には必ず利用されますので、興味がありましたらご一読いただけたら幸いです。

https://docs.microsoft.com/ja-jp/azure/application-gateway/multiple-site-overview#host-headers-and-server-name-indication-sni

## ホストヘッダーとは
HTTP ヘッダーの一つで "Host: xxx" といった形式で定義されます。HTTP ヘッダーには Location, User-Agent, connection 等様々なヘッダーがありますが、ホストヘッダーは HTTP リクエストのホスト名を定義するためのものです。ブラウザを開いてる状態で F12 を押し、ネットワークタブからも確認することができます。

以下はマイクロソフトのドキュメントにアクセスした際に確認できたホストヘッダの例です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/3d431c2b-a75b-4aab-3389-b79bcc105b51.png)

Azure で提供されている PaaS の多くはマルチテナント型で、複数のユーザーで同じ環境を共有する形となりますが、Web サービスの場合はこの HTTP リクエスト内のホストヘッダーと PaaS に設定した FQDN が一致しているかを確認し、各ユーザーに割り当てられた Web サイトに対するリクエストであるかどうかを判定しております。※ https の通信については後述。

## Server Name Indication とは
SNI は TLS (HTTPS [HTTP Secure] とほぼ同義) の拡張機能の一つで TLS handshake (TLS の通信を確立する仕組み) の中でクライアントが接続する FQDN をサーバー側に伝えるための仕組みとなります。TLS handshake と似た言葉に TCP 3way handshake がありますが、これは TCP の通信を確立する仕組みで、TLS handshake は TCP の通信が確立した状態で行われますので、TLS handshake の方がより上位レイヤーです。

SNI はブラウザの機能では確認ができないので、パケットキャプチャにて確認する必要があります。

以下はマイクロソフトのドキュメントサイトに Edge ブラウザでアクセスした際のクライアント側のパケットキャプチャです。
TCP 3way handshake (青枠) の直後に Client Hello (赤枠) がクライアントから送られ、そのパケット内に Server Name Indicate extension として Server Name : docs.microsoft.com が入っていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/1262ee3a-9d55-f082-023e-9a919b95e100.png)

Web サーバー側は上記の SNI を基にサーバー側の証明書の FQDN またはドメイン名と照らし合わせ、TLS handshake の処理を進めます。TLS の handshake が完了した後に、HTTP リクエストがクライアントから Web サーバーへ送られ、レスポンスが返ったタイミングで Web ページがブラウザ上に表示されるようになります。

## SNI がサポートされないクライアント
最新の PC やモバイル端末であれば問題ありませんが、ガラケーや古い端末ではサポートされていない場合があるので注意が必要です。

他のサイトですが、SNI 対応リストもあります。
https://rms.ne.jp/sslserver/basis/sni-support-server-browser/

SNI がサポートされていないクライアントですと、FQDN を指定して Web サーバーにアクセスした場合でも、SNI は使われない動作となります。SNI がない場合、Azure FrontDoor や AppService 等 SNI を前提としたサービスには https でアクセスできませんので注意が必要です。

https://"IP アドレス" といった形で IP アドレスで Web サイトにアクセスした場合も同様に SNI は使われません。Azure Firewall のアプリケーションルールは https の通信可否を SNI で評価するため、SNI がない場合、評価できず拒否されます。

[「Action: Deny. Reason: SNI TLS extension was missing」で拒否される理由を教えてください。](https://jpaztech.github.io/blog/network/firewall-rules/#faq)

SNI がない場合、以下の様に Client Hello のパケット内に Extension として server_name がありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/0452b7b4-f9b7-212b-8f5e-7b88c9f9643d.png)

## カスタムドメインとは
クラウド基盤が払い出す既定のドメインではなくユーザーが所有しているドメインのことをカスタムドメインと呼びます。

## 実際の動作のイメージ図

以下は Web サイトをブラウザで開いた際の一連の処理のイメージ図です。⑤で Server Name Indication を利用し、⑥で HTTP ホストヘッダーを利用して Web サービスにアクセスすることで適切な Web ページにアクセスすることができます。
この例ですと SNI, ホストヘッダーとして testsite1.hiyamanet2.com が利用され、Web サービスはその FQDN に該当する Web ページを返します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/28b22150-0ba1-9b00-2e50-da281da8a8fd.png)

また、CDN, WAF やリバースプロキシ等を経由する構成にしたときに、要件によっては リバースプロキシとバックエンドの Web サービスの両方で同じカスタムドメインを割り当てないといけない場合があります。例えばドメイン属性をもつクッキーや Location ヘッダー等がある場合です。Azure でよくある例は以下を参照ください。

https://jpaztech.github.io/blog/network/appgw-appservice-redirectissue/

このような構成の場合、以下の例のようにそれぞれのサービスにアクセスするための FQDN はループしないように異なるものにする必要があります。さらに FQDN としては共通のカスタムドメインも別途割り当てる必要があり、混乱しやすいため、**"名前解決に利用する FQDN"** と **"TLS の SNI"** , **"HTTP のホストヘッダー"** は分けて考えることをお勧めします。
以下の例では 1-1, 2-1 の FQDN は異なっていますが、1-5,2-5 の SNI と 1-6,2-6 のホストヘッダーは同じになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/a78895e7-d0f9-cef8-b562-3ca3f1f57921.png)

Edge,Chrome 等の一般的な Web ブラウザを使った場合、URL 内の FQDN = (名前解決に利用する FQDN, TLS の SNI, HTTP のホストヘッダー) となりますが、クライアントとして curl, openssl 等を使うことでそれぞれの FQDN を変更することができます。

## curl コマンドによる検証

Windows の WSL の機能を使って、curl コマンドで意図的に TLS の SNI, HTTP のホストヘッダーを異なる FQDN にしてみます。
curl コマンドの場合、アクセスの URL で SNI が決まりますので、FQDN が異なるホストヘッダーをオプションで指定して実行してます。-v をつけるとリクエストヘッダーを確認可能です。

`curl -v https://<FQDN [SNI]> -H 'Host: <FQDN [ホストヘッダー]>'`

以下が実行時のログですが、URL で指定した FQDN と証明書の FQDN は一致していますが、ホストヘッダーは異なるホスト名になっていることを確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/e4a66120-8491-c9c3-6348-f4a3e92e9b10.png)

TLS の SNI, HTTP ホストヘッダーを指定した状態で、DNS を利用した名前解決とは異なる IP アドレスにアクセスしたい場合、--resolve オプションが便利です。以下の様に --resolve オプションを付けて DNS の名前解決とは異なる IP アドレス指定することで、TLS の SNI, HTTP ホストヘッダーを指定することができます。

curl -v https://testsite1.hiyamanet2.com -H 'Host: hiyamaafdtest-fderebenechrfyfb.z01.azurefd.net' **--resolve testsite1.hiyamanet2.com:13.107.213.46**

## openssl コマンドによる検証

openssl で s_client というサブコマンドと -connect オプションを使うことで、接続先の IP アドレス、-servername オプションで TLS の SNI を指定することができます。※servername オプションがない場合、SNI なしでの接続となります。
また、TLS handshake が確立した状態で HTTP リクエストを送ることもできますので、openssl s_client コマンド実行後に、以下のような文字列を一行ずつ入力していき、Enter キーを押すことで HTTP のヘッダーを指定してリクエストを送ることができます。

`openssl s_client -connect <IP アドレス>:443 -servername <FQDN [SNI]>`
～TLS handshake～
`GET / HTTP/1.1`
`Host: <FQDN [ホストヘッダー]>`
`Enter キー`

以下はリクエストヘッダーをレスポンスとして返してくれる php のページに openssl を使ってアクセスした際のログとなります。
まず最初に -servername オプションで指定した FQDN が証明書のドメインと一致していることを確認できます。
その後、HTTP リクエストのホストヘッダーで指定した FQDN がリクエストヘッダーとして Web サーバーに送られていることを確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2266293/85e66e08-9db7-96b8-a3ba-c462ec83a021.png)

## ドメインフロンティングとは
今まで記載した通り、クライアントによっては TLS の SNI と HTTP のホストヘッダーの指定を変えることができますが、この仕組みを利用した攻撃はドメインフロンティング攻撃と呼ばれています。
以下にも例がありますが、例えばドメインフロンティングを用いることで、本来制限がかかるべきドメインに対して、異なる SNI で接続してアクセスするといったことが可能になってしまう場合があります。

https://news.microsoft.com/ja-jp/2021/04/09/210409-securing-our-approach-to-domain-fronting-within-azure/

以下の通り、Azure Front Door や Azure CDN ではすでに対策を進めており、サポートリクエストを上げることでドメインフロンティングとみなされたリクエストをブロックすることができるとのことです。

https://docs.microsoft.com/ja-jp/azure/frontdoor/front-door-faq#front-door---------------------------------------

**Front Door は、"ドメイン フロンティング" の動作をどのようにして処理するのですか?**
`2022 年 4 月 29 日現在、Microsoft は、Azure Front Door (Standard、Premium、クラシック) と Azure CDN の動作を、そのプラットフォームでのドメイン フロンティング動作を禁止するというコミットメントに沿って変更しました。`
`ドメイン フロンティングをブロックしたい場合は、サポート リクエストを作成して、ご利用のサブスクリプションとリソース情報をお知らせください。`