AWS Network FirewallでSNIスプーフィング対策としてTLSインスペクションを有効化してみた
SNI スプーフィング対策をしたい
こんにちは、のんピ(@non____97)です。
皆さんは SNI スプーフィング対策をしたいなと思ったことはありますか? 私はあります。
AWS Network Firewall を使ってドメインフィルタリングをされたいケースがあると思います。
しかし、通常の AWS Network Firewall ではHostヘッダーや SNI の Server Name を元に Allow/Drop/Reject を判定しています。
そのため、これらを詐称してしまえばすり抜けることが可能です。
re:Postでも紹介されていますね。
こちらの対応の一つに TLS インスペクションが挙げられます。
- But what if I want to directly block client side SNI manipulation with AWS Network Firewall?
That can be accomplished by enabling AWS Network Firewall’s TLS decryption feature. When TLS decrypt is enabled, client side SNI manipulation is blocked by default and this error message is displayed in the firewall’s TLS log:
{ "firewall_name": "NetworkFirewall", "availability_zone": "us-east-1a", "event_timestamp": 1727451885, "event": { "timestamp": "2024-09-27T15:44:45.321222Z", "src_ip": "10.2.1.145", "src_port": "39038", "dest_ip": "44.193.128.70", "dest_port": "443", "sni": "spoofedsni.com", "tls_error": { "error_message": "SNI: spoofedsni.com Match Failed to server certificate names: checkip.us-east-1.prod.check-ip.aws.a2z.com/checkip.us-east-1.prod.check-ip.aws.a2z.com/checkip.amazonaws.com/checkip.check-ip.aws.a2z.com " } } }
AWS Network Firewall Best Practices - AWS Security Services Best Practices
実際に TLS インスペクションをすることで SNI スプーフィングを回避できるか確認してみます。
いきなりまとめ
- SNIスプーフィング対策としてTLSインスペクションは有効
- TLSインスペクションを有効化することで、指定されたSNIのServer Nameと送信先の証明書名の不整合に検知することが可能
- ただし、CAおよびCA証明書の管理、CA証明書の管理が課題
やってみた
検証環境
検証環境は以下のとおりです。
AWS Network Firewallに関連づけているファイアウォールポリシーでは以下設定をしています。
- ルールの順序 : 厳密な順序 (strict order)
- デフォルトのアクション : 確立された接続のパケットをドロップ (drop established)
要するに暗黙的な拒否が行われています。ルール順序やデフォルトアクションの詳細な説明は以下記事をご覧ください。
ステートフルルールグループはrule1とrule2を割り当てています。
rule1では以下のようにdev.classmethod.jp
もしくは*.classmethod.jp
のドメインを許可するようにSuricataルールを設定しています。
pass tls $HOME_NET any -> $EXTERNAL_NET any (tls.sni; content:"dev.classmethod.jp"; startswith; nocase; endswith; msg:"Pass dev.classmethod.jp"; flow:to_server; sid:100; rev:1;)
pass tls $HOME_NET any -> $EXTERNAL_NET any (tls.sni; dotprefix; content:".classmethod.jp"; nocase; endswith; msg:"Pass *.classmethod.jp"; flow:to_server; sid:101; rev:1;)
drop http $HOME_NET any -> $EXTERNAL_NET any (http.header_names; content:"|0d 0a|"; startswith; msg:"Drop invalid HTTP"; flow:to_server; sid:500; rev:1;)
drop tls $HOME_NET any -> $EXTERNAL_NET any (msg:"Drop all other TLS"; flow:to_server; sid:9999; rev:1;)
rule2では以下のように*.amazonaws.com
のドメインを許可するようにルールを設定しています。SSM Session Managerで接続するために使用しています。
> aws network-firewall describe-rule-group \
--rule-group-arn arn:aws:network-firewall:us-east-1:<AWSアカウントID>:stateful-rulegroup/rule2
{
"UpdateToken": "b0b500ba-00c2-4253-99c6-c3bb06cd67e2",
"RuleGroup": {
"RulesSource": {
"RulesSourceList": {
"Targets": [
".amazonaws.com"
],
"TargetTypes": [
"HTTP_HOST",
"TLS_SNI"
],
"GeneratedRulesType": "ALLOWLIST"
}
},
"StatefulRuleOptions": {
"RuleOrder": "STRICT_ORDER"
}
},
"RuleGroupResponse": {
"RuleGroupArn": "arn:aws:network-firewall:us-east-1:<AWSアカウントID>:stateful-rulegroup/rule2",
"RuleGroupName": "rule2",
"RuleGroupId": "10b356ef-f157-427c-a0b0-07e91223f3c4",
"Type": "STATEFUL",
"Capacity": 100,
"RuleGroupStatus": "ACTIVE",
"Tags": [],
"ConsumedCapacity": 3,
"NumberOfAssociations": 1,
"EncryptionConfiguration": {
"KeyId": "AWS_OWNED_KMS_KEY",
"Type": "AWS_OWNED_KMS_KEY"
},
"LastModifiedTime": "2025-10-02T19:14:19.212000+09:00"
}
}
rule1が先に判定されてしまうとsid:9999
のルールで拒否されてしまうので、rule2の優先度が高くなるように設定しています。
SNI スプーフィングする先として、nfw-test.www.non-97.net
というドメインを割り当てた ALB を用意しました。こちらの ALB は HTTP と HTTPS のリスナーを持っており、それぞれ HTTP 200 の固定レスポンスを返すようにしています。
TLS インスペクション有効前
まずはTLSインスペクション有効前の動作確認をしましょう。
許可されているドメインの通信が正常に行えるか
許可されているドメインを含むhttps://dev.classmethod.jp/
にアクセスをします。
$ curl https://dev.classmethod.jp/ -v \
-s \
2>&1 \
| grep -Ev '(<.*>|bytes data]|\{.*\})'
* Host dev.classmethod.jp:443 was resolved.
* IPv6: 2600:9000:27ce:5400:14:e623:c740:93a1, 2600:9000:27ce:c00:14:e623:c740:93a1, 2600:9000:27ce:f400:14:e623:c740:93a1, 2600:9000:27ce:4c00:14:e623:c740:93a1, 2600:9000:27ce:8e00:14:e623:c740:93a1, 2600:9000:27ce:ec00:14:e623:c740:93a1, 2600:9000:27ce:7e00:14:e623:c740:93a1, 2600:9000:27ce:b400:14:e623:c740:93a1
* IPv4: 3.167.99.118, 3.167.99.98, 3.167.99.120, 3.167.99.14
* Trying [2600:9000:27ce:5400:14:e623:c740:93a1]:443...
* Immediate connect fail for 2600:9000:27ce:5400:14:e623:c740:93a1: Network is unreachable
.
.
(中略)
.
.
* Trying [2600:9000:27ce:b400:14:e623:c740:93a1]:443...
* Immediate connect fail for 2600:9000:27ce:b400:14:e623:c740:93a1: Network is unreachable
* Trying 3.167.99.118:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/pki/tls/certs/ca-bundle.crt
* CApath: none
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / x25519 / id-ecPublicKey
* ALPN: server accepted h2
* Server certificate:
* subject: CN=*.classmethod.jp
* start date: Apr 20 00:00:00 2025 GMT
* expire date: May 20 23:59:59 2026 GMT
* subjectAltName: host "dev.classmethod.jp" matched cert's "*.classmethod.jp"
* issuer: C=US; O=Amazon; CN=Amazon ECDSA 256 M04
* SSL certificate verify ok.
* Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
* Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
* Certificate level 2: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
* Connected to dev.classmethod.jp (3.167.99.118) port 443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://dev.classmethod.jp/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: dev.classmethod.jp]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.11.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: dev.classmethod.jp
> User-Agent: curl/8.11.1
> Accept: */*
>
* Request completely sent off
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/2 200
< content-type: text/html; charset=utf-8
< cache-control: public, max-age=45, stale-if-error=21600
< date: Fri, 03 Oct 2025 08:18:36 GMT
< x-custom-lang: ja
< x-middleware-rewrite: /ja
< x-powered-by: Next.js
< x-envoy-upstream-service-time: 133
< server: envoy
< vary: Accept-Encoding
< x-cache: Miss from cloudfront
< via: 1.1 6028cf6b68ccf308226eae7dc6c6af42.cloudfront.net (CloudFront)
< x-amz-cf-pop: IAD55-P7
< alt-svc: h3=":443"; ma=86400
< x-amz-cf-id: 6XR6lkWFcGO6uA_zZdkToWCO5edMAxgDYSg51OC6tEpI0T_latPw7Q==
< server-timing: cdn-upstream-layer;desc="REC",cdn-upstream-dns;dur=0,cdn-upstream-connect;dur=0,cdn-upstream-fbl;dur=278,cdn-cache-miss,cdn-pop;desc="IAD55-P7",cdn-rid;desc="6XR6lkWFcGO6uA_zZdkToWCO5edMAxgDYSg51OC6tEpI0T_latPw7Q==",cdn-downstream-fbl;dur=284
<
はい、問題なくHTTP 200が返ってきました。
許可されていないドメインの通信が正常に拒否されるか
続いて、許可されていないドメインの通信が正常に拒否されるかを確認します。
nfw-test.www.non-97.net
へHTTPとHTTPSでアクセスします。
# HTTP
$ curl http://nfw-test.www.non-97.net/ \
-v \
-s \
-m 5
* Host nfw-test.www.non-97.net:80 was resolved.
* IPv6: (none)
* IPv4: 3.93.18.208, 3.233.32.245
* Trying 3.93.18.208:80...
* Connected to nfw-test.www.non-97.net (3.93.18.208) port 80
* using HTTP/1.x
> GET / HTTP/1.1
> Host: nfw-test.www.non-97.net
> User-Agent: curl/8.11.1
> Accept: */*
>
* Request completely sent off
* Operation timed out after 5001 milliseconds with 0 bytes received
* closing connection #0
# HTTPS
$ curl https://nfw-test.www.non-97.net/ \
-v \
-s \
-m 5
* Host nfw-test.www.non-97.net:443 was resolved.
* IPv6: (none)
* IPv4: 3.93.18.208, 3.233.32.245
* Trying 3.93.18.208:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/pki/tls/certs/ca-bundle.crt
* CApath: none
* Connection timed out after 5001 milliseconds
* closing connection #0
どちらも意図したとおり拒否されましたね。デフォルトアクションのdrop establishedが正常に動作していそうです。
スプーフィングを行うことで許可されていないドメインの通信が拒否されないか
スプーフィングを行うことで許可されていないドメインの通信が拒否されないか確認します。
許可されているドメインssm.amazonaws.com
やdev.classmethod.jp
をHostヘッダーやServer Nameに指定して、実際にはnfw-test.www.non-97.net
へアクセスをします。
まずはHTTPです。
$ curl http://$(dig +short nfw-test.www.non-97.net | head -1)/ \
-H "Host: ssm.amazonaws.com" \
-v \
-s
* Trying 3.93.18.208:80...
* Connected to 3.93.18.208 (3.93.18.208) port 80
* using HTTP/1.x
> GET / HTTP/1.1
> Host: ssm.amazonaws.com
> User-Agent: curl/8.11.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Server: awselb/2.0
< Date: Fri, 03 Oct 2025 07:47:24 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 11
< Connection: keep-alive
<
* Connection #0 to host 3.93.18.208 left intact
HTTP non-97
$ curl http://$(dig +short nfw-test.www.non-97.net | head -1)/ \
-H "Host: dev.classmethod.jp" \
-v \
-s \
-m 5
* Trying 3.233.32.245:80...
* Connected to 3.233.32.245 (3.233.32.245) port 80
* using HTTP/1.x
> GET / HTTP/1.1
> Host: dev.classmethod.jp
> User-Agent: curl/8.11.1
> Accept: */*
>
* Request completely sent off
* Operation timed out after 5002 milliseconds with 0 bytes received
* closing connection #0
はい、ssm.amazonaws.com
を使用した場合はHTTP non-97
が返っていることから拒否されずに通信できてしまいました。
一方、dev.classmethod.jp
の場合はrule1でHTTP通信に関するルールを設定していないことから通信に失敗していることが分かります。
続いて、HTTPSの場合です。
$ curl https://ssm.amazonaws.com/ \
--resolve ssm.amazonaws.com:443:$(dig +short nfw-test.www.non-97.net | head -1) \
-H "Host: nfw-test.www.non-97.net" \
-k \
-v \
-s
* Added ssm.amazonaws.com:443:3.233.32.245 to DNS cache
* Hostname ssm.amazonaws.com was found in DNS cache
* Trying 3.233.32.245:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / x25519 / RSASSA-PSS
* ALPN: server accepted h2
* Server certificate:
* subject: CN=nfw-test.www.non-97.net
* start date: Oct 3 00:00:00 2025 GMT
* expire date: Nov 1 23:59:59 2026 GMT
* issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M04
* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 2: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Connected to ssm.amazonaws.com (3.233.32.245) port 443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://ssm.amazonaws.com/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: nfw-test.www.non-97.net]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.11.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: nfw-test.www.non-97.net
> User-Agent: curl/8.11.1
> Accept: */*
>
* Request completely sent off
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/2 200
< server: awselb/2.0
< date: Fri, 03 Oct 2025 07:50:43 GMT
< content-type: text/plain; charset=utf-8
< content-length: 12
<
* Connection #0 to host ssm.amazonaws.com left intact
HTTPS non-97
$ curl https://dev.classmethod.jp/ \
--resolve dev.classmethod.jp:443:$(dig +short nfw-test.www.non-97.net | head -1) \
-H "Host: nfw-test.www.non-97.net" \
-k \
-v \
-s
* Added dev.classmethod.jp:443:3.93.18.208 to DNS cache
* Hostname dev.classmethod.jp was found in DNS cache
* Trying 3.93.18.208:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / x25519 / RSASSA-PSS
* ALPN: server accepted h2
* Server certificate:
* subject: CN=nfw-test.www.non-97.net
* start date: Oct 3 00:00:00 2025 GMT
* expire date: Nov 1 23:59:59 2026 GMT
* issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M04
* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 2: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Connected to dev.classmethod.jp (3.93.18.208) port 443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://dev.classmethod.jp/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: nfw-test.www.non-97.net]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.11.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: nfw-test.www.non-97.net
> User-Agent: curl/8.11.1
> Accept: */*
>
* Request completely sent off
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/2 200
< server: awselb/2.0
< date: Fri, 03 Oct 2025 07:51:11 GMT
< content-type: text/plain; charset=utf-8
< content-length: 12
<
* Connection #0 to host dev.classmethod.jp left intact
HTTPS non-97
はい、どちらもHTTPS non-97
が返ってきていることからSNIスプーフィングが成功していることが分かります。
ということで、厳密なドメインフィルタリングが求められる場合はドメインリスト、Suricataルールのいずれのルールグループでも不十分であることが分かります。
Subject Alternative Name(SAN)による検証
ふと、ubject Alternative Name(SAN)による検証ができるのか気になったので試してみます。
tls.subjectaltname
を用いてSANの内容を確認するように、rule1を以下のとおり変更しようとしてみます。
pass tls $HOME_NET any -> $EXTERNAL_NET any (tls.subjectaltname; content:"classmethod.jp"; nocase; endswith; msg:"Pass dev.classmethod.jp"; flow:to_server; sid:100; rev:1;)
drop http $HOME_NET any -> $EXTERNAL_NET any (http.header_names; content:"|0d 0a|"; startswith; msg:"Drop invalid HTTP"; flow:to_server; sid:500; rev:1;)
drop tls $HOME_NET any -> $EXTERNAL_NET any (msg:"Drop all other TLS"; flow:to_server; sid:9999; rev:1;)
すると、以下のようにtls.subjectaltname
というキーワードは知らないととエラーが出力されました。
stateful rule is invalid, reason: unknown rule keyword 'tls.subjectaltname'. stateful rule is invalid, reason: error parsing signature "pass tls $HOME_NET any -> $EXTERNAL_NET any (tls.subjectaltname; content:"classmethod.jp"; nocase; endswith; msg:"Pass dev.classmethod.jp"; flow:to_server; sid:100; rev:1; gid:1234567890;) stateful rule is invalid, reason: Loading signatures failed.
2025/10/4時点のAWS Network FirewallはSuricata 7.0互換です。
Network Firewall upgraded from Suricata version 6.0.9 to 7.0 in November of 2024. For full information about the upgrade from version 6.0.9, see Upgrading 6.0 to 7.0.
Working with stateful rule groups in AWS Network Firewall - AWS Network Firewall
以下Suricata 7.0のドキュメントを確認すると、確かにtls.subjectaltname
はありませんでした。残念。
Common Name(CN)による検証
SANが使えないならCommon Name(CN)でできるか試しましょう。
rule1を以下のようなルールに変更します。
# 目的
## - SNIの検証
# 動作
## 1. クライアントがTLS Client Helloを送信したとき
## 2. SNIが classmethod.jp で終わるかチェック
## 3. マッチしたら、接続先サーバーのIPアドレス単位で"allowed_sni_destination_ips"フラグを10秒間セット
## 4. アラートは出さない(noalert)
alert tls $HOME_NET any -> $EXTERNAL_NET 443 (ssl_state:client_hello; tls.sni; content:"classmethod.jp"; endswith; nocase; xbits:set, allowed_sni_destination_ips, track ip_dst, expire 10; noalert; sid:238745;)
# 目的
## - TLSハンドシェイクを進行させる
## - サーバーから証明書を受け取れるようにする
# 動作
## 1. クライアント→サーバーのTCP/443トラフィックをチェック
## 2. 接続先IPに"allowed_sni_destination_ips"フラグがセットされているか確認
## 3. フラグがあれば、TCPパケットを許可
pass tcp $HOME_NET any -> $EXTERNAL_NET 443 (xbits:isset, allowed_sni_destination_ips, track ip_dst; flow: stateless; sid:89207006;)
# 目的
## - 定義した証明書のCNが正しいかの検証
# 動作
## 1. サーバー → クライアントのTLSトラフィックをチェック
## 2. 証明書のSubject(CN)が classmethod.jp で終わるか確認
## 3. かつ、このサーバーIPに「allowed_sni_destination_ips」フラグがあるか確認
## 4. 両方YESならパケットを許可
pass tls $EXTERNAL_NET 443 -> $HOME_NET any (tls.cert_subject; content:"classmethod.jp"; endswith; msg:"Pass rules do not alert"; xbits:isset, allowed_sni_destination_ips, track ip_src; sid:29822;)
# 目的
## - 定義した証明書のCNが正しいかの検証
# 動作
## 1. サーバー → クライアントのTLSトラフィックをチェック
## 2. 証明書のSubjectに . が含まれるか確認(sid:29822にマッチしなかった全ての通信)
## 3. かつ、このサーバーIPに「allowed_sni_destination_ips」フラグがあるか確認
## 4. 両方YESならパケット拒否
reject tls $EXTERNAL_NET 443 -> $HOME_NET any (tls.cert_subject; content:"."; xbits:isset,allowed_sni_destination_ips,track ip_src; msg:"Reject cert mismatch"; sid:29823;)
許可されているドメインへ通常に通信を行う場合は許可されることを確認します。
$ curl https://dev.classmethod.jp/ \
-v \
-s \
-m 5 \
--tls-max 1.2 \
2>&1 \
| grep -Ev '(<.*>|bytes data]|\{.*\})'
* Host dev.classmethod.jp:443 was resolved.
* IPv6: 2600:9000:27ce:5800:14:e623:c740:93a1, ..(中略).. 2600:9000:27ce:b400:14:e623:c740:93a1
* IPv4: 3.167.99.98, 3.167.99.118, 3.167.99.120, 3.167.99.14
* Trying [2600:9000:27ce:5800:14:e623:c740:93a1]:443...
* Immediate connect fail for 2600:9000:27ce:5800:14:e623:c740:93a1: Network is unreachable
.
.
(中略)
.
.
* Immediate connect fail for 2600:9000:27ce:de00:14:e623:c740:93a1: Network is unreachable
* Trying [2600:9000:27ce:b400:14:e623:c740:93a1]:443...
* Immediate connect fail for 2600:9000:27ce:b400:14:e623:c740:93a1: Network is unreachable
* Trying 3.167.99.98:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/pki/tls/certs/ca-bundle.crt
* CApath: none
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-ECDSA-AES128-GCM-SHA256 / x25519 / id-ecPublicKey
* ALPN: server accepted h2
* Server certificate:
* subject: CN=*.classmethod.jp
* start date: Apr 20 00:00:00 2025 GMT
* expire date: May 20 23:59:59 2026 GMT
* subjectAltName: host "dev.classmethod.jp" matched cert's "*.classmethod.jp"
* issuer: C=US; O=Amazon; CN=Amazon ECDSA 256 M04
* SSL certificate verify ok.
.
.
(中略)
.
.
* Connected to dev.classmethod.jp (3.167.99.98) port 443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://dev.classmethod.jp/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: dev.classmethod.jp]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.11.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: dev.classmethod.jp
> User-Agent: curl/8.11.1
> Accept: */*
>
* Request completely sent off
< HTTP/2 200
< content-type: text/html; charset=utf-8
< cache-control: public, max-age=45, stale-if-error=21600
< date: Fri, 03 Oct 2025 10:21:16 GMT
.
.
(中略)
.
.
<
それではSNIスプーフィングを行います。
$ curl https://dev.classmethod.jp/ \
--resolve dev.classmethod.jp:443:$(dig +short nfw-test.www.non-97.net | head -1) \
-H "Host: nfw-test.www.non-97.net" \
-k \
-v \
-s \
-m 5 \
--tls-max 1.2
* Added dev.classmethod.jp:443:3.93.18.208 to DNS cache
* Hostname dev.classmethod.jp was found in DNS cache
* Trying 3.93.18.208:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* Connection timed out after 5002 milliseconds
* closing connection #0
* RESOLVE dev.classmethod.jp:443 - old addresses discarded
* Added dev.classmethod.jp:443:3.93.18.208 to DNS cache
* Could not resolve host: method.jp
* shutting down connection #1
はい、拒否されました。
AWS Network FirewallをAlertログを確認すると、sid:29823
のルールで拒否されたことが分かります。
{
"firewall_name": "non-97-nfw",
"availability_zone": "us-east-1a",
"event_timestamp": "1759494595",
"event": {
"tx_id": 0,
"app_proto": "tls",
"src_ip": "3.233.32.245",
"src_port": 443,
"event_type": "alert",
"alert": {
"severity": 3,
"signature_id": 29823,
"rev": 0,
"signature": "Reject cert mismatch",
"action": "blocked",
"category": ""
},
"flow_id": 988786731858430,
"dest_ip": "10.20.8.109",
"proto": "TCP",
"verdict": {
"action": "drop",
"reject-target": "to_client",
"reject": [
"tcp-reset"
]
},
"tls": {
"subject": "CN=nfw-test.www.non-97.net",
"issuerdn": "C=US, O=Amazon, CN=Amazon RSA 2048 M04",
"serial": "0F:61:01:76:3F:0E:98:A1:D7:59:C8:17:9B:18:33:9B",
"fingerprint": "35:68:7b:54:57:1b:6b:6f:f2:61:37:c4:9f:e4:50:09:85:2b:98:ff",
"sni": "dev.classmethod.jp",
"version": "TLS 1.2",
"notbefore": "2025-10-03T00:00:00",
"notafter": "2026-11-01T23:59:59"
},
"dest_port": 49024,
"pkt_src": "geneve encapsulation",
"timestamp": "2025-10-03T12:29:55.771528+0000",
"direction": "to_client"
}
}
ただし、お気づきの方がいるように--tls-max 1.2
を指定しています。
ではオプションをつけずにTLS 1.3の通信で実際に試してみましょう。
$ curl https://dev.classmethod.jp/ \
-v \
-s \
-m 5 \
--tls-max 1.3 \
2>&1 \
| grep -Ev '(<.*>|bytes data]|\{.*\})'
* Host dev.classmethod.jp:443 was resolved.
* IPv6: 2600:9000:27ce:f200:14:e623:c740:93a1, ..(中略).. 2600:9000:27ce:800:14:e623:c740:93a1
* IPv4: 3.167.99.120, 3.167.99.98, 3.167.99.118, 3.167.99.14
* Trying [2600:9000:27ce:f200:14:e623:c740:93a1]:443...
* Immediate connect fail for 2600:9000:27ce:f200:14:e623:c740:93a1: Network is unreachable
.
.
(中略)
.
.
* Immediate connect fail for 2600:9000:27ce:f000:14:e623:c740:93a1: Network is unreachable
* Trying [2600:9000:27ce:800:14:e623:c740:93a1]:443...
* Immediate connect fail for 2600:9000:27ce:800:14:e623:c740:93a1: Network is unreachable
* Trying 3.167.99.120:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/pki/tls/certs/ca-bundle.crt
* CApath: none
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / x25519 / id-ecPublicKey
* ALPN: server accepted h2
* Server certificate:
* subject: CN=*.classmethod.jp
* start date: Apr 20 00:00:00 2025 GMT
* expire date: May 20 23:59:59 2026 GMT
* subjectAltName: host "dev.classmethod.jp" matched cert's "*.classmethod.jp"
* issuer: C=US; O=Amazon; CN=Amazon ECDSA 256 M04
* SSL certificate verify ok.
.
.
(中略)
.
.
* Connected to dev.classmethod.jp (3.167.99.120) port 443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://dev.classmethod.jp/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: dev.classmethod.jp]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.11.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: dev.classmethod.jp
> User-Agent: curl/8.11.1
> Accept: */*
>
* Request completely sent off
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/2 200
< content-type: text/html; charset=utf-8
< cache-control: public, max-age=45, stale-if-error=21600
< date: Fri, 03 Oct 2025 10:24:47 GMT
.
.
(中略)
.
.
<
はい、TLS 1.3の場合はSNIスプーフィングを突破してしまいました。
これはTLS 1.3の場合は証明書の情報も暗号化されいるため、AWS Network Firewallで検出できないためです。
Client Server
Key ^ ClientHello
Exch | + key_share*
| + signature_algorithms*
| + psk_key_exchange_modes*
v + pre_shared_key* -------->
ServerHello ^ Key
+ key_share* | Exch
+ pre_shared_key* v
{EncryptedExtensions} ^ Server
{CertificateRequest*} v Params
{Certificate*} ^
{CertificateVerify*} | Auth
{Finished} v
<-------- [Application Data*]
^ {Certificate*}
Auth | {CertificateVerify*}
v {Finished} -------->
[Application Data] <-------> [Application Data]
+ Indicates noteworthy extensions sent in the
previously noted message.
* Indicates optional or situation-dependent
messages/extensions that are not always sent.
{} Indicates messages protected using keys
derived from a [sender]_handshake_traffic_secret.
[] Indicates messages protected using keys
derived from [sender]_application_traffic_secret_N.
Figure 1: Message Flow for Full TLS Handshake
[RFC 8446 \- The Transport Layer Security \(TLS\) Protocol Version 1\.3](https://datatracker.ietf.org/doc/html/rfc8446)
実際にWiresharkでパケットキャプチャをすると、TLS 1.2の場合は証明書の各種情報を確認できますが、TLS 1.3の場合はServer Hello以降は通信が暗号化されていることが分かります。
- TLS1.2の場合
- TLS1.3の場合
ということで、TLS 1.3の前に証明書を内容を確認するルールは無力です。
なお、SNIスプーフィングをしなければ許可されていないServer NameおよびCNの証明書を使用していなければ拒否されます。
$ curl http://nfw-test.www.non-97.net/ \
-v \
-s \
-m 5
* Host nfw-test.www.non-97.net:80 was resolved.
* IPv6: (none)
* IPv4: 3.93.18.208, 3.233.32.245
* Trying 3.93.18.208:80...
* Connected to nfw-test.www.non-97.net (3.93.18.208) port 80
* using HTTP/1.x
> GET / HTTP/1.1
> Host: nfw-test.www.non-97.net
> User-Agent: curl/8.11.1
> Accept: */*
>
* Request completely sent off
* Operation timed out after 5002 milliseconds with 0 bytes received
* closing connection #0
TLS インスペクション有効化
SNIスプーフィング対策は難しいということが分かったことなので、TLS インスペクションの有効化を行います。
以下記事を参考に設定をします。
まずはTLSインスペクションの作成に必要なRoot CA証明書の作成です。
# 証明書に発行なディレクトリやファイルの作成
$ sudo mkdir -p /etc/pki/CA/private
$ sudo mkdir -p /etc/pki/CA/newcerts
$ sudo touch /etc/pki/CA/index.txt
# keyUsageのして
$ sudo sed -i "s/^# \(keyUsage = cRLSign, keyCertSign\)/\1/g" /etc/pki/tls/openssl.cnf
# CA証明書のCSRの発行
$ sudo openssl req \
-new \
-config /etc/pki/tls/openssl.cnf \
-out /etc/pki/CA/root-ca-csr.pem \
-keyout /etc/pki/CA/private/cakey.pem
....
.
.
(中略)
.
.
+++
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:JP
State or Province Name (full name) []:Tokyo
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server\'s hostname) []:root-ca.corp.non-97.net
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
# CA証明書の発行
$ sudo openssl ca \
-config /etc/pki/tls/openssl.cnf \
-extensions v3_ca \
-create_serial \
-selfsign \
-in /etc/pki/CA/root-ca-csr.pem
Using configuration from /etc/pki/tls/openssl.cnf
Enter pass phrase for /etc/pki/CA/private/cakey.pem:
Check that the request matches the signature
Signature ok
Certificate Details:
Serial Number:
2f:c1:a5:7d:ef:a0:cf:6b:a2:d2:0d:a4:86:9a:d6:1e:25:b5:9b:30
Validity
Not Before: Oct 3 11:16:12 2025 GMT
Not After : Oct 3 11:16:12 2026 GMT
Subject:
countryName = JP
stateOrProvinceName = Tokyo
organizationName = Default Company Ltd
commonName = root-ca.corp.non-97.net
X509v3 extensions:
.
.
(中略)
.
.
X509v3 Basic Constraints: critical
CA:TRUE
X509v3 Key Usage:
Certificate Sign, CRL Sign
Certificate is to be certified until Oct 3 11:16:12 2026 GMT (365 days)
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
2f:c1:a5:7d:ef:a0:cf:6b:a2:d2:0d:a4:86:9a:d6:1e:25:b5:9b:30
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=JP, ST=Tokyo, O=Default Company Ltd, CN=root-ca.corp.non-97.net
Validity
Not Before: Oct 3 11:16:12 2025 GMT
Not After : Oct 3 11:16:12 2026 GMT
Subject: C=JP, ST=Tokyo, O=Default Company Ltd, CN=root-ca.corp.non-97.net
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
.
.
(中略)
.
.
Exponent: 65537 (0x10001)
X509v3 extensions:
.
.
(中略)
.
.
X509v3 Basic Constraints: critical
CA:TRUE
X509v3 Key Usage:
Certificate Sign, CRL Sign
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
.
.
(中略)
.
.
-----BEGIN CERTIFICATE-----
.
.
(中略)
.
.
-----END CERTIFICATE-----
Database updated
# 秘密鍵のデコード
$ sudo openssl rsa \
-in /etc/pki/CA/private/cakey.pem \
-out /etc/pki/CA/private/cakey_decoded.pem
Enter pass phrase for /etc/pki/CA/private/cakey.pem:
writing RSA key
$ sudo cat /etc/pki/CA/private/cakey_decoded.pem
-----BEGIN PRIVATE KEY-----
.
.
(中略)
.
.
-----END PRIVATE KEY-----
発行されたRoot CA証明書を信頼できるCA証明書として読み込んでおきます。
$ openssl crl2pkcs7 \
-nocrl \
-certfile /etc/pki/tls/certs/ca-bundle.crt \
| openssl pkcs7 -print_certs \
| grep subject \
| grep root-ca.corp.non-97.net
$ sudo vi /etc/pki/ca-trust/source/anchors/root-ca.corp.non-97.net
$ cat /etc/pki/ca-trust/source/anchors/root-ca.corp.non-97.net
-----BEGIN CERTIFICATE-----
.
.
(中略)
.
.
-----END CERTIFICATE-----
$ sudo update-ca-trust
$ openssl crl2pkcs7 \
-nocrl \
-certfile /etc/pki/tls/certs/ca-bundle.crt \
| openssl pkcs7 -print_certs \
| grep subject \
| grep root-ca.corp.non-97.net
subject=C=JP, ST=Tokyo, O=Default Company Ltd, CN=root-ca.corp.non-97.net
発行されたRoot CA証明書とCSRの署名で使用した秘密鍵をACMにインポートします。
TLSインスペクションに必要なTLS検査設定を作成します。
先ほど作成したRoot CA証明書をアウトバウンドSSL/TLSインスペクション用のCA証明書として指定します。
TLS検査設定の名前や説明を指定します。
スコープを定義します。今回は全IPアドレス間のTCP/443宛の通信をスコープとします。
執行している証明書やステータスが不明な証明書を使用している場合拒否するように設定します。
オプションの詳細は以下AWS公式ドキュメントをご覧ください。
設定内容を確認して作成します。
既存のファイアウォールポリシーにTLS検査設定を追加することはできません。そのため、ファイアウォールポリシーを作成します。
先ほど作成したTLS検査設定を指定します。他は既存のファイアウォールポリシーと同じです。
rule1のポリシーを以下のように最初の動作確認時に使用したものに戻しておきます。
pass tls $HOME_NET any -> $EXTERNAL_NET any (tls.sni; content:"dev.classmethod.jp"; startswith; nocase; endswith; msg:"Pass dev.classmethod.jp"; flow:to_server; sid:100; rev:1;)
pass tls $HOME_NET any -> $EXTERNAL_NET any (tls.sni; dotprefix; content:".classmethod.jp"; nocase; endswith; msg:"Pass *.classmethod.jp"; flow:to_server; sid:101; rev:1;)
drop http $HOME_NET any -> $EXTERNAL_NET any (http.header_names; content:"|0d 0a|"; startswith; msg:"Drop invalid HTTP"; flow:to_server; sid:500; rev:1;)
drop tls $HOME_NET any -> $EXTERNAL_NET any (msg:"Drop all other TLS"; flow:to_server; sid:9999; rev:1;)
最後にAWS Network Firewallに割り当てるファイアウォールポリシーを先ほど作成したTLS検査設定に変更します。
TLS インスペクション有効後
それでは実際に試してみましょう。
EC2インスタンスにはSSM Session Managerで接続をしていたのですが、どこからか接続ができなくなりました。
AWS Network FirewallのTLSログを確認すると、以下のとおり証明書関連のエラーになっていました。
{
"firewall_name": "non-97-nfw",
"availability_zone": "us-east-1a",
"event_timestamp": 1759491878,
"event": {
"timestamp": "2025-10-03T11:44:38.612217Z",
"src_ip": "10.20.8.109",
"src_port": "46244",
"dest_ip": "13.220.37.7",
"dest_port": "443",
"sni": "ssm.us-east-1.amazonaws.com",
"tls_error": {
"error_message": "Error in connection to Client: Bad certificate."
}
}
}
EC2 Instance Connect Endpointを作成して接続を行い、SSM Agentを再起動したところ正常にSSM Session Managerで接続できるようになりました。CA証明書を読み込むためにサービスの再起動が必要なものがあることは考慮が必要ですね。
許可されているドメインにアクセスをします。
$ curl https://dev.classmethod.jp/ -I
HTTP/2 200
content-type: text/html; charset=utf-8
cache-control: public, max-age=45, stale-if-error=21600
date: Fri, 03 Oct 2025 11:45:48 GMT
.
.
(中略)
.
.
x-cache: Miss from cloudfront
via: 1.1 71b24e89f6f9e648d6cc206b3f6cc3da.cloudfront.net (CloudFront)
x-amz-cf-pop: IAD55-P7
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: KBb31_NUedwr62YEir02Dlh1Ehs4v4Bhel8lLB40Lt6QmiOy35w_sw==
server-timing: cdn-upstream-layer;desc="REC",cdn-upstream-dns;dur=0,cdn-upstream-connect;dur=144,cdn-upstream-fbl;dur=622,cdn-cache-miss,cdn-pop;desc="IAD55-P7",cdn-rid;desc="KBb31_NUedwr62YEir02Dlh1Ehs4v4Bhel8lLB40Lt6QmiOy35w_sw==",cdn-downstream-fbl;dur=629
正常にアクセスできました。
許可されていないドメインへアクセスした時、意図したとおり拒否されるか確認をします。
$ curl https://nfw-test.www.non-97.net/ \
-v \
-s \
-m 5
* Host nfw-test.www.non-97.net:443 was resolved.
* IPv6: (none)
* IPv4: 3.93.18.208, 3.233.32.245
* Trying 3.93.18.208:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/pki/tls/certs/ca-bundle.crt
* CApath: none
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / x25519 / RSASSA-PSS
* ALPN: server accepted h2
* Server certificate:
* subject: CN=nfw-test.www.non-97.net
* start date: Oct 2 11:42:31 2025 GMT
* expire date: Oct 2 11:42:31 2026 GMT
* subjectAltName: host "nfw-test.www.non-97.net" matched cert's "nfw-test.www.non-97.net"
* issuer: C=JP; ST=Tokyo; O=Default Company Ltd; CN=root-ca.corp.non-97.net
* SSL certificate verify ok.
* Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Connected to nfw-test.www.non-97.net (3.93.18.208) port 443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://nfw-test.www.non-97.net/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: nfw-test.www.non-97.net]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.11.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: nfw-test.www.non-97.net
> User-Agent: curl/8.11.1
> Accept: */*
>
* Request completely sent off
* Recv failure: Connection reset by peer
* OpenSSL SSL_read: Connection reset by peer, errno 104
* Failed receiving HTTP2 data: 56(Failure when receiving data from the peer)
* Connection #0 to host nfw-test.www.non-97.net left intact
それではTLS 1.3を使用した場合のSNIスプーフィングが成立するか確認をします。
$ curl https://ssm.amazonaws.com/ \
--resolve ssm.amazonaws.com:443:$(dig +short nfw-test.www.non-97.net | head -1) \
-H "Host: nfw-test.www.non-97.net" \
-k \
-v \
-s \
-m 5
* Added ssm.amazonaws.com:443:3.233.32.245 to DNS cache
* Hostname ssm.amazonaws.com was found in DNS cache
* Trying 3.233.32.245:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* Recv failure: Connection reset by peer
* TLS connect error: error:00000000:lib(0)::reason(0)
* OpenSSL SSL_connect: Connection reset by peer in connection to ssm.amazonaws.com:443
* closing connection #0
$ curl https://dev.classmethod.jp/ \
--resolve dev.classmethod.jp:443:$(dig +short nfw-test.www.non-97.net | head -1) \
-H "Host: nfw-test.www.non-97.net" \
-k \
-v \
-s \
-m 5
* Added dev.classmethod.jp:443:3.93.18.208 to DNS cache
* Hostname dev.classmethod.jp was found in DNS cache
* Trying 3.93.18.208:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* Recv failure: Connection reset by peer
* TLS connect error: error:00000000:lib(0)::reason(0)
* OpenSSL SSL_connect: Connection reset by peer in connection to dev.classmethod.jp:443
* closing connection #0
いずれも拒否されましたね。
この時のTLSログを確認すると、以下のようにSNIのServer Nameとサーバー証明書のドメインが一致していないことによってエラーになっていることが分かります。
{
"firewall_name": "non-97-nfw",
"availability_zone": "us-east-1a",
"event_timestamp": 1759492287,
"event": {
"timestamp": "2025-10-03T11:51:27.807253Z",
"src_ip": "10.20.8.109",
"src_port": "59200",
"dest_ip": "3.233.32.245",
"dest_port": "443",
"sni": "ssm.amazonaws.com",
"tls_error": {
"error_message": "SNI: ssm.amazonaws.com Match Failed to server certificate names: nfw-test.www.non-97.net/nfw-test.www.non-97.net "
}
}
}
{
"firewall_name": "non-97-nfw",
"availability_zone": "us-east-1a",
"event_timestamp": 1759492321,
"event": {
"timestamp": "2025-10-03T11:52:01.719193Z",
"src_ip": "10.20.8.109",
"src_port": "50666",
"dest_ip": "3.93.18.208",
"dest_port": "443",
"sni": "dev.classmethod.jp",
"tls_error": {
"error_message": "SNI: dev.classmethod.jp Match Failed to server certificate names: nfw-test.www.non-97.net/nfw-test.www.non-97.net "
}
}
}
Alertログには以下のような記録がされていました。
{
"firewall_name": "non-97-nfw",
"availability_zone": "us-east-1a",
"event_timestamp": "1759492026",
"event": {
"tx_id": 2,
"app_proto": "http2",
"src_ip": "10.20.8.109",
"src_port": 42838,
"event_type": "alert",
"alert": {
"severity": 3,
"signature_id": 500,
"rev": 1,
"signature": "Drop invalid HTTP",
"action": "blocked",
"category": ""
},
"flow_id": 723968711961187,
"dest_ip": "3.93.18.208",
"proto": "TCP",
"verdict": {
"action": "drop"
},
"http": {
"version": "2",
"request_headers": [
{
"name": ":method",
"value": "GET"
},
{
"name": ":scheme",
"value": "https"
},
{
"name": ":authority",
"value": "nfw-test.www.non-97.net"
},
{
"name": ":path",
"value": "/"
},
{
"name": "user-agent",
"value": "curl/8.11.1"
},
{
"name": "accept",
"value": "*/*"
}
],
"http_method": "GET",
"url": "/",
"http_user_agent": "curl/8.11.1",
"http2": {
"stream_id": 1,
"request": {},
"response": {}
}
},
"dest_port": 443,
"timestamp": "2025-10-03T11:47:06.168585+0000",
"direction": "to_server"
}
}
{
"firewall_name": "non-97-nfw",
"availability_zone": "us-east-1a",
"event_timestamp": "1759492026",
"event": {
"app_proto": "http2",
"src_ip": "10.20.8.109",
"src_port": 42838,
"event_type": "alert",
"alert": {
"severity": 3,
"signature_id": 9999,
"rev": 1,
"signature": "Drop all other TLS",
"action": "blocked",
"category": ""
},
"flow_id": 723968711961187,
"dest_ip": "3.93.18.208",
"proto": "TCP",
"verdict": {
"action": "drop"
},
"dest_port": 443,
"timestamp": "2025-10-03T11:47:06.168585+0000",
"direction": "to_server"
}
}
厳密なドメインフィルタリングが必要な場合はTLSインスペクションの導入も検討しよう
AWS Network FirewallでSNIスプーフィングに対策としてTLSインスペクションを使ってみました。
通常のドメインフィルタリングが全く無駄かというとそうではありません。誤ってアクセスしてしまうことを防ぐ際には十分な効果があるでしょう。
内部からの悪意あるリクエストを防ぐためには不十分であるシチュエーションもあるでしょう。また、現状はHostヘッダーの詐称はAWS Network Firewallは対応できません。そのような場合はSquidなどのWebプロキシーを挟むなどの別の対応が必要になると考えます。
Azure FirewallにおいてはHTTPの場合にクライアントの名前解決結果をそのまま利用するのではなく、Azure FirewallがHostヘッダーの値を元に名前解決するようです。
HTTP および TLS インスペクションが有効である HTTPS では、ファイアウォールでパケットの宛先 IP アドレスが無視され、ホスト ヘッダーからの DNS 解決済み IP アドレスが使用されます。
都度Azure Network Firewallで名前解決するのであればパフォーマンス影響もあると思いますが、嬉しい場面もあるでしょう。AWS Network Firewallも選択できるようになると嬉しいですね。
厳密なドメインフィルタリングが必要な場合はTLSインスペクションの導入も検討しましょう。
ただし、CAおよびCA証明書の管理が課題になります。特にEast-West、North-Southで中央集権的にAWS Network Firtewallで各ネットワーク間のトラフィックを制御している環境においては影響範囲的に簡単に導入できるものではないと感じます。
この記事が誰かの助けになれば幸いです。
以上、クラウド事業本部 コンサルティング部の のんピ(@non____97)でした!