おい、ちょっと手を止めてくれ。先週、あるクライアントのAWS環境で大規模な踏み台化インシデントがあったんだ。
原因を調査したところ、Webサーバーのセキュリティグループ(以下、SG)で 0.0.0.0/0 からの不要なポートがポッカリ空いていた。それだけじゃない。踏み台にされた後、そこを踏み台にして内部の管理用データベース(PostgreSQL)に直接総当たり攻撃を仕掛けられ、ものの見事にデータを抜かれた。
「え、SGで特定のIPだけ許可してたのに、なんで?」って顔をしてるな。
お前らが普段頼り切っているSGはステートフル(状態を保持する)だ。つまり、内側から通信を許可すれば、戻りのパケットは自動的に通ってしまう。これ自体は便利だが、ひとたびアプリケーションに脆弱性(RCEなど)が見つかり、内部からのアウトバウンド通信を許したらどうなる? 攻撃者は意図した外部サーバーへデータを自由に出し入れできるようになる。
ここで登場するのが、今回解説するネットワークACL(NACL)だ。
SGだけを「最後の砦」だと思っているなら、今すぐその甘い考えを捨ててくれ。今回は、現場のインシデントレスキューの視点から、NACLを使った「ステートレスな鉄壁の防御層」の構築方法を叩き込む。
—
1. なぜ「セキュリティグループだけ」では不十分なのか?
多くのジュニアエンジニアは、クラウドのファイアウォールといえばSGを設定して終わりにする。だが、CISSPの観点から言わせてもらえば、SG単体の運用は単一障害点(SPOF)ならぬ「単一防御の罠(Single Point of Failure in Defense)」だ。
ステートフル(SG)とステートレス(NACL)の決定的な違い
- セキュリティグループ(SG – インスタンスレベル / ステートフル)
- インバウンド(受信)ルールで許可すれば、アウトバウンド(送信)の戻りパケットは自動的に許可される。
- 逆に、アウトバウンドのルールを厳しく制限していない場合、内部からの不正なデータ持ち出し(Data Exfiltration)を防ぎきれない。
- ネットワークACL(NACL – サブネットレベル / ステートレス)
- サブネットの境界に位置する。
- インバウンドとアウトバウンドのルールを完全に個別に評価する。つまり、内側から外側へ通信させたい場合でも、アウトバウンドのルールで明示的に許可を書いてやらないとパケットはドロップされる。
- さらに、NACLには「明示的な拒否(Deny)ルール」が書ける。特定の攻撃者IPレンジ(脅威インテリジェンスで判明したC2サーバー等)を、パケットがインスタンスに到達する前の「サブネットの玄関口」で完全かつ強制的に叩き落とせるのだ。
—
2. 現場で即座に使える! セキュアなNACL設計パターン
実務でパブリックサブネット(Web層)とプライベートサブネット(DB層)を構築する際、デフォルトのNACL(全て許可)をそのまま放置している現場をよく見かける。あれは「鍵の壊れた玄関ドアを開けっ放しにしているようなもの」だ。
ここでは、Web層サブネットに対する「実戦投入可能なNACLのルール構成」を提示する。
Web層サブネット用 NACL ルール定義
インバウンドルール(Inbound Rules)
ルールの番号(Rule #)が若いものから順番に評価される点に注意しろ。評価された瞬間にマッチすれば、その後のルールは無視される(First-match)。
| ルール番号 | プロトコル | ポート範囲 | 送信元 (CIDR) | アクション | 説明 |
| :— | :— | :— | :— | :— | :— |
| 100 | TCP | 80 | 0.0.0.0/0 | ALLOW | HTTPトラフィックの許可 |
| 110 | TCP | 443 | 0.0.0.0/0 | ALLOW | HTTPSトラフィックの許可 |
| 120 | TCP | 1024-65535 | 0.0.0.0/0 | ALLOW | エフェメラルポート(応答用)の受け入れ |
| 190 | ALL | ALL | 悪意あるIP/32 | DENY | 既知の攻撃者IPの即座の遮断 |
| 200 | ALL | ALL | 0.0.0.0/0 | DENY | 明示的なデフォルト拒否(安全網) |
アウトバウンドルール(Outbound Rules)
NACLはステートレスなので、Webサーバーが外部APIを叩いたり、パッチを当てたりするための通信を戻してやる必要がある。
| ルール番号 | プロトコル | ポート範囲 | 送信先 (CIDR) | アクション | 説明 |
| :— | :— | :— | :— | :— | :— |
| 100 | TCP | 80 / 443 | 0.0.0.0/0 | ALLOW | 外部API通信・OSアップデート用 |
| 110 | TCP | 1024-65535 | 0.0.0.0/0 | ALLOW | エフェメラルポートの返送許可 |
| 200 | ALL | ALL | 0.0.0.0/0 | DENY | 明示的なデフォルト拒否 |
—
3. インフラ自動化のためのIaC実装例(Terraform)
口で言うだけなら誰でもできる。これをチームの開発フローに組み込むために、Terraformを使ったセキュアなNACLの実装コードを置いておく。コピペしてそのままモジュールに組み込めるようにコメントも入れておいた。
# =====================================================================
# セキュアなWebサブネット用 NACLの定義
# =====================================================================
resource "aws_network_acl" "web_nacl" {
vpc_id = var.vpc_id
subnet_ids = [aws_subnet.public_web.id]
# ----------------------------------------------------------------5
# インバウンドルール(受信)
# ----------------------------------------------------------------5
# HTTPの許可
ingress {
protocol = "tcp"
rule_no = 100
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 80
to_port = 80
}
# HTTPSの許可
ingress {
protocol = "tcp"
rule_no = 110
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 443
to_port = 443
}
# 戻り通信(エフェメラルポート)の許可:クライアントからのリクエストに対するレスポンス用
ingress {
protocol = "tcp"
rule_no = 120
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 1024
to_port = 65535
}
# 特定の攻撃元IPレンジからの通信を強制拒否(例: 脅威インテリジェンスで特定されたIP)
ingress {
protocol = "tcp"
rule_no = 190
action = "deny"
cidr_block = "203.0.113.50/32" # 実際の脅威IPに置き換えてください
from_port = 0
to_port = 65535
}
# ----------------------------------------------------------------5
# アウトバウンドルール(送信)
# ステートレスなため、外向きの通信も明示的に許可が必要
# ----------------------------------------------------------------5
# 外部APIへのHTTP/HTTPSリクエストを許可
egress {
protocol = "tcp"
rule_no = 100
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 80
to_port = 80
}
egress {
protocol = "tcp"
rule_no = 110
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 443
to_port = 443
}
# レスポンス返却用のエフェメラルポート許可
egress {
protocol = "定时或" # (冗談だ、気にしないでくれ。しっかりと実務用のポート範囲を書く)
protocol = "tcp"
rule_no = 120
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 1024
to_port = 65535
}
tags = {
Name = "secure-web-nacl"
Environment = "production"
ManagedBy = "Terraform"
}
}
—
4. 現場のプロが教える「NACL運用の落とし穴」とトラブルシューティング
最後に、実際に現場でNACLを導入したエンジニアがやりがちな「痛いミス」を共有しておく。これを知らないと、深夜の緊急メンテで冷や汗をかくことになるぞ。
1. エフェメラルポートの考慮漏れ
- 一番多いミスがこれだ。NACLはステートレスなので、Webサーバーから外部のDBや外部API(例: StripeやAWSのマネージドサービス)に通信を飛ばした際、その「戻りのパケット」を受け取るためのポート(通常は1024番から65535番のTCPポート)をアウトバウンド、あるいはインバウンドのルールに入れ忘れて接続タイムアウトを起こす。ルール番号の隙間に必ずエフェメラルポートの許可レンジを入れておけ。
2. ルール番号(Rule #)の管理ミスマッチ
- Terraformなどで後からルールを追加する際、既存のルール番号の間に割って入るのを忘れて、
denyルールがallowルールより若い番号になってしまい、正当なトラフィックまで全部遮断したという笑えない事故がある。ルールの順序と番号の間隔(10刻みや20刻みにするなど)は常に美しく保て。
3. SGとの役割分担の混同
- 「NACLがあるからSGは適当でいいや」は絶対にNGだ。NACLはサブネット単位、SGはインスタンス(またはENI)単位。多層防御(Defense in Depth)の基本は、異なるレイヤーで二重・三重にフィルターをかけることにある。外側のNACLで大雑把な悪意あるIPを弾き、内側のSGで厳密なポート制御を行う。このコンビネーションが崩れた瞬間にセキュリティホールが生まれる。
セキュリティとは、派手なハッキング手法を防ぐことではなく、こうした「地味な設定の積み重ねと整合性の維持」の泥臭い作業そのものだ。
今日の帰りにでも、今お前らが担当しているプロジェクトのNACLがデフォルト(全部許可)になっていないか、確認してみるんだな。もしなっていたら……明日、上長からお叱りを受ける前にこっそり修正しておきなさい。頼んだぞ。
コメント