【実務・中級編】 IAMロールの信頼ポリシー(Trust Policy)におけるConfused Deputy問題の回避 – インフラ・ネットワーク & クラウドセキュリティ防御ガイド

権限を「貸す」という悪夢: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 ブロックが空になっていないか確認してくれ。それが、今日の君の最初で最高の仕事になるはずだ。

コメント

タイトルとURLをコピーしました