権限を「貸す」という悪夢:IAMロールのConfused Deputy問題をどう封じ込めるか
現場でインフラを触っていると、「利便性」という甘い言葉で、セキュリティの根幹を揺るがす設計に出くわすことがよくある。君たちがクラウドでIAMロールを組む時、「とりあえずクロスアカウントアクセスを通すために、信頼ポリシーを広く開放してしまった」なんてことはないだろうか?
今日は、攻撃者が最も好む「Confused Deputy(混乱した代理人)問題」を、実務レベルでどう叩き潰すかについて話そう。教科書的な「最小権限の原則」を唱えるのは簡単だが、現場でどう防ぐかが重要だ。
—
1. なぜ「Confused Deputy」は防げないのか?
Confused Deputy問題とは、簡単に言えば「権限のある誰か(代理人)」が、「悪意ある第三者」に騙されて、本来アクセスしてはいけないリソースを操作させられてしまう脆弱性だ。
例えば、SaaSベンダーに君たちのAWS環境へのアクセス権を渡す際、信頼ポリシーを単に Principal: {AWS: "SaaSのAWSアカウントID"} とだけ設定していないか?
これだと、そのSaaSベンダーが管理する「別の顧客」が、SaaSのプラットフォームを経由して君たちの環境へアクセスを試みた場合、AWSのAPIは「あ、このSaaSのアカウントからのリクエストだね、OK!」と許可を出してしまう。これが悪夢の始まりだ。
—
2. 「防御の二段構え」で完全に封殺する
この攻撃を防ぐには、信頼ポリシーに「特定の呼び出し元」と「特定のコンテキスト」を強制的に紐付ける必要がある。具体的には以下の2つを併用する。
1. aws:SourceArn または aws:SourceAccount: 「誰からのリクエストか」を物理的に制限する。
2. sts:ExternalId: 二要素認証に近い概念で、リソース所有者とサービスプロバイダー間で共有された「合言葉」を要求する。
—
3. 実践:セキュアなIAM信頼ポリシーの構築
以下は、外部サービス(SaaS等)に自社アカウントのロールを渡す際の、模範的な信頼ポリシーだ。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "MY_SECRET_COMPANY_ID_12345"
},
"ArnLike": {
"aws:SourceArn": "arn:aws:states:us-east-1:123456789012:stateMachine:MyTargetStateMachine"
}
}
}
]
}
ここが防衛のポイント:
sts:ExternalId: これをSaaS側の設定画面にも入力させることで、万が一SaaS側の設定が漏洩しても、君たちが発行した固有のIDを知らなければロールを引き継げない。aws:SourceArn: リクエスト元を特定のARNに限定する。これにより、たとえ同じAWSアカウント内の別のリソースが悪用されても、このポリシーは拒否される。
—
4. アプリケーション層での検証(Python実装)
もし君たちが、外部システムからのリクエストを受け付ける中継サーバーを構築しているなら、IAMロールをAssumeする前に、必ず「外部IDの検証」をコードレベルで行うべきだ。
import boto3
def assume_role_with_security(role_arn, external_id):
# ロールを引き受ける際にExternalIdを付与
sts_client = boto3.client('sts')
try:
assumed_role = sts_client.assume_role(
RoleArn=role_arn,
RoleSessionName="ExternalServiceSession",
ExternalId=external_id # ここで事前に合意したIDを渡す
)
return assumed_role['Credentials']
except Exception as e:
# ここでログを出力し、不正なアクセス試行を監視する
print(f"セキュリティ警告: 不正なAssumeRole試行を検知: {e}")
return None
—
5. エンジニアとして忘れてはならないこと
「動く設定」を作るのは、ジュニアエンジニアの仕事だ。しかし、「動くし、かつ攻撃者が侵入する隙間がどこにもない設定」を作るのが、我々シニアの仕事だ。
1. 最小権限の徹底: AssumeRole の対象となるロールには、必要なS3バケットやDynamoDBテーブル以外へのアクセス権を絶対につけるな。
2. CloudTrailの監視: AssumeRole イベントを監視し、requestParameters.externalId が一致しないリクエストが頻発していないかアラートを飛ばせ。
3. 定期的監査: 半年に一度は、IAMロールの信頼ポリシーを確認し、「不要なPrincipalが含まれていないか」を確認するスクリプトを走らせること。
セキュリティとは、穴を塞ぐ作業の積み重ねだ。面倒に感じるかもしれないが、インシデントで顧客の信頼と君たちの休日を失うよりは、遥かにマシなコストだと思わないか?
さあ、今すぐ君のTerraformやCloudFormationのテンプレートを開いて、Condition ブロックが空になっていないか確認してくれ。それが、今日の君の最初で最高の仕事になるはずだ。
コメント