説明
サーバーを 0.0.0.0 や空のホストアドレスにバインドすると、複数のネットワークインターフェースでリクエストを受け付けます。実際に外部から到達できるかは、経路やファイアウォールにも依存します。全インターフェースでの待ち受け自体が認証を回避するわけではありませんが、内部専用サービスの認証・TLS・ネットワーク制御が公開範囲に合っていないと、機密情報や管理機能を意図せず公開するおそれがあります。
想定される影響
- 外部から到達でき、十分に保護されていない管理・デバッグ・メトリクスのエンドポイントから、設定、トークン、セッション情報が漏えいする可能性があります。
- 内部ネットワーク専用の機能へ外部クライアントが直接リクエストを送れる場合があります。実際の操作権限は認証・認可によって決まります。
- 不要に公開されたサービスが、脆弱なバージョンやアカウントへの攻撃の入口になるおそれがあります。
対処方法
- 待ち受けアドレスを明示してください。内部専用サービスでは、
net.Listenやtls.Listenにループバックまたは必要なプライベートIPアドレスを指定してください。 - 環境ごとにアドレスとポートを管理し、本番で全インターフェースを使う場合は意図した公開か確認してください。
- 外部公開が不要なポートは、ファイアウォールやクラウドのセキュリティグループで承認済みネットワーク・IPアドレスに制限してください。
- リバースプロキシを使う場合、同じホスト・ネットワーク名前空間ならループバック、別のホストやコンテナーなら必要なプライベート接続経路にバックエンドへのアクセスを制限してください。認証と転送の保護も適用してください。
- 実際の待ち受けソケットと外部から到達できるポートを定期的に確認してください。
例
変更前
go
package main
import (
"log"
"net"
)
// 内部管理用TCPサーバーの例
func startAdminServer() {
// 全インターフェース(0.0.0.0)で待ち受け、内部管理ポートが外部へ露出するおそれ
listener, err := net.Listen("tcp", "0.0.0.0:9000")
if err != nil {
log.Fatal(err)
}
defer listener.Close()
for {
conn, err := listener.Accept()
if err != nil {
log.Println("accept error:", err)
continue
}
go func(c net.Conn) {
defer c.Close()
c.Write([]byte("admin status: OK\n"))
}(conn)
}
}
func main() {
startAdminServer()
}
変更後
go
package main
import (
"log"
"net"
"os"
)
// 内部管理用TCPサーバー(ローカルまたはプライベートアドレスにバインド)
func startAdminServer() {
// 環境変数でアドレスを設定し、既定ではlocalhostに制限
addr := os.Getenv("ADMIN_BIND_ADDR")
if addr == "" {
// 127.0.0.1だけにバインドし、ローカルからの接続に限定
addr = "127.0.0.1:9000"
}
listener, err := net.Listen("tcp", addr)
if err != nil {
log.Fatal(err)
}
defer listener.Close()
log.Printf("admin server listening on %s", addr)
for {
conn, err := listener.Accept()
if err != nil {
log.Println("accept error:", err)
continue
}
go func(c net.Conn) {
defer c.Close()
c.Write([]byte("admin status: OK\n"))
}(conn)
}
}
func main() {
startAdminServer()
}
解説:
- 変更前:
0.0.0.0:9000で待ち受けます。外部からの経路が許可されていれば、認証なしでステータス応答を取得できます。 - 変更後: 既定のアドレスは
127.0.0.1:9000です。ADMIN_BIND_ADDRを設定するとその値をそのまま使うため、信頼できる管理者がデプロイ設定を管理し、実際のアクセス範囲を確認する必要があります。