はじめに

平素は大変お世話になっております。
クイックガードのパー子です。

混乱した代理問題 (Confused Deputy Problem) という情報セキュリティ上のよく知られた危険があります。
AWS の公式ドキュメント でも IAM を使用するうえでの注意事項として丸々1ページを割いて解説されています。

これを読めば概念としては理解できるのですが、ただ読むだけでは今ひとつ実感を得難く、

「現実ではどのように悪用されるのか? 挙げられている対策を実施することで確かに防げるものなのか?」

という疑念が拭えません。

そこで、あえて脆弱性を抱えたデモアプリケーションを作成し、混乱した代理問題を実演することで理解を深めてみることにしました。
AWS における混乱した代理問題の中でも特に クロスアカウントで権限委任するケース に着目し、「SaaSベンダーの提供する自動化サービスに自身の IAMロールを委ねる」というシナリオで、その自動化サービスを実装&実演します。

シナリオ概要

「ユーザから IAMロールを預かって、AWS の利用料金を取得&表示する」という架空の SaaS を考えます。

登場人物

登場人物は 3者です。

名称役割SaaS内でのユーザ名AWSアカウントID
有栖川さんSaaS の善良な利用者。今回のシナリオでの犠牲者となる。alice111122223333
爆上BobBob社SaaS の運営会社。社名が何かに似ている?N/A444455556666
麻呂・リー氏攻撃者。有栖川さんの IAMロール名を推測して悪用する。mallory777788889999

デモ環境

  1. ログイン
  2. IAMロールの入力
  3. 利用料金の表示

の 3ページで構成される、ごく簡単な Webアプリケーションをサッと作ってみました。

ソースコードは GitHub で公開しています。
AI に作らせたので中身は超適当です。

https://github.com/quickguard-oss/confused-deputy-demo

普通に使ってみる

攻撃の前に、まずは普通に使ってみます。

善良な利用者である有栖川さんがユーザ名 alice でログインします。

ログインしたら、委任する IAMロールを指定します。

許可ポリシー:

AWS Cost Explorer からデータを取得するので、ce:GetCostAndUsage を許可します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "ce:GetCostAndUsage",
      "Resource": "*"
    }
  ]
}

信頼ポリシー:

SaaS運営会社 (AWSアカウントID: 444455556666) からの AssumeRole を許可します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::444455556666:root"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

上記のポリシーで作成したロールの ARN (ここでは arn:aws:iam::111122223333:role/alice とします) を入力します。

正常に有栖川さんの AWSアカウントの利用料金が表示されました。

攻撃してみる

それでは実際に攻撃してみましょう。

攻撃者である麻呂・リー氏になりきって操作します。
ログイン時のユーザ名は mallory です。

麻呂・リー氏は事前に有栖川さんの身辺情報を探っており、有栖川さんが使用する IAMロールは arn:aws:iam::111122223333:role/alice であると推測していました。
これをおもむろに入力します。

すると、攻撃は成功し、有栖川さんの AWSアカウントの利用料金 が表示されてしまいました。

原因と対策

攻撃が成立してしまった原因は、デモアプリケーションの「ユーザから渡された IAMロールを検証なしに受け入れ、そのまま行使している」という実装にあります。
提示されたロールが本当に当該ユーザに属しているのか、あるいは当該ユーザのために行使してよいものかを確認するプロセスが欠落していた結果、攻撃者は他者のロールARN を入力するだけで権限を肩代わりさせることに成功してしまうのです。

したがって、ユーザが提示したロールを申告のままに使うのではなく、何らかの手段でユーザとロールの関係性を検証すればこの問題を防ぐことができます。

そして、AWS はこの検証のために External ID という仕組みを用意しています。

  1. SaaS運営会社は、External ID としてユーザごとに一意で推測困難な秘匿文字列を払い出す。

  2. ユーザは、IAMロールの信頼ポリシーで External ID の一致判定を行うようにする。

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
            "AWS": "arn:aws:iam::444455556666:root"
          },
          "Action": "sts:AssumeRole",
          "Condition": {
            "StringEquals": {
              "sts:ExternalId": "<当該ユーザ専用の External ID>"
            }
          }
        }
      ]
    }
    
  3. SaaS運営会社は、AssumeRole する際に、各ユーザごとの External ID を一緒に送付する。

External ID は SaaS運営会社が発行/管理し、各ユーザとの二者間でのみ共有されるので、ユーザ本人であることを認証するシークレットとして機能するのです。
第三者に推測されてしまっては意味がないので、連番や、ユーザの属性に関係する値などは避けなければなりません。

補足

この仕組みにおいて重要なのは “二者間での秘匿” という点なので、必ずしもサービス側が External ID を決定するのではなく、「ユーザ側から希望する ID を申請する」という方式でもよいかもしれません。

対策版

今度は External ID による検証を組み込んだ “セキュアモード” でデモアプリケーションを動かしてみます。

先ほどと同じように、

  1. まずは有栖川さんが普通に利用する。
  2. そのあとに麻呂・リー氏が有栖川さんの IAMロールを騙る。

という流れをなぞり、どのような結果となるか見ていきましょう。

Side: 有栖川さん

ユーザ名 alice でログインします。

続いて IAMロールの入力画面です。

セキュアモードでは External ID を設定するように案内が表示されます。
(信頼ポリシーの記述例に Conditionブロックが追加されています)

信頼ポリシー:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::444455556666:root"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "ext_f9eedf6f6e3627f1"
        }
      }
    }
  ]
}

指示どおりに IAMロールの信頼ポリシーに Conditionブロックを書き足してから先に進むと、以前と同様に問題なく利用料金が表示されます。

Side: 麻呂・リー氏

ユーザ名 mallory でログインし、攻撃を仕掛けてみます。
今度はどうなるでしょうか。

提示されている External ID が有栖川さんとは異なりますが、構わず有栖川さんを騙った IAMロール arn:aws:iam::111122223333:role/alice を入力してみます。

すると… やりました。
AssumeRole に失敗して、有栖川さんのロールを行使できませんでした。

目論見どおり External ID による検証が奏功して、混乱した代理問題を防げたことを確認できました。

おさらい:「混乱した代理問題」とは

詳しくは AWS の公式ドキュメントなどを参照していただくとして、今回の題材とした “クロスアカウントで権限委任するケース” について簡潔に説明すると、以下の 3点に要約できます。

  1. ある主体 A (= 利用者; 有栖川さん) が、主体 B (= 代理者としての SaaS; 爆上BobBob社) に自分の代わりに何かをしてもらうため、B に権限を与える。
  2. ここで、B は、誰のために処理しているかを明示的に識別/検証しないまま権限を行使する処理フローとなっている。
  3. その結果、別の主体 C (= 攻撃者; 麻呂・リー氏) が B を欺いて A を装うと、B は C の利益のために A のリソースを参照/操作してしまう。

AWS では、一般的に権限委任には AssumeRole を用いて IAMロールを渡します。
IAMロールの ARN は以下の形式となっており、

arn:aws:iam::<AWSアカウントID>:role/<ロール名>

AWSアカウントID は基本的に 秘匿情報とは見なされておらず、また、ロール名も人間が見てわかりやすい名前をつけることが多いため、第三者に推測される可能性があります。

提示された IAMロールは正当に所有されているものなのか?
その検証が欠落すると、クロスアカウントでの委任において混乱した代理問題が発生してしまうのです。

まとめ

AWS における混乱した代理問題について、脆弱性を抱えたデモアプリケーションを用いてクロスアカウントで権限委任するシナリオを実演し、攻撃がどのように成立するかを体験しました。

提示されたロールが本当にそのユーザに属するかを検証するプロセスが欠落していた場合、攻撃者が他者の IAMロールARN を推測できれば、当該ロールを所有していないにもかかわらず権限を行使できてしまうことを確認しました。
そして、AWS が提供する External ID をユーザごとに一意で推測困難な値として付与することで、ユーザとロールの関係性を検証し、権限の不正利用を阻止できることを示しました。

当記事が混乱した代理問題を理解する一助となれば幸いです。

今後ともよろしくお願い申し上げます。


当記事の図表には AWS Architecture Icons を使用しています。