Category: Cloud · IAM · Containers
Level: Practitioner
There is a persistent mental model in infrastructure security that frames privilege as a binary: you either have root or you don't. In cloud environments, this model is dangerously incomplete.
A container running as a non-root user, with no shell, no tools, and limited file read access, can still be the starting point for a full cross-account compromise — if the workload identity attached to it has the wrong permissions.
Every major cloud provider exposes a metadata service at a link-local address (typically 169.254.169.254). This endpoint is reachable from any process running on the host or inside a container, unless explicitly blocked. It serves:
Querying it requires nothing more than an HTTP request:
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Returns: my-app-role
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/my-app-role
# Returns: AccessKeyId, SecretAccessKey, Token, Expiration
If a vulnerability in the application allows any form of outbound HTTP request or file read, this endpoint is reachable. Server-side request forgery (SSRF), path traversal, dependency vulnerabilities — any of these can be the entry point.
The credentials returned are temporary but fully functional AWS credentials tied to the IAM role attached to the workload. Their blast radius depends entirely on what that role is allowed to do.
In a real exercise, a role that could not list all buckets, could not create instances, and had no obvious administrative access still allowed:
That was enough. An old container image in the registry had a .env file baked into one of its layers. The .env had credentials for another service. That service had a cross-account trust relationship. The chain ended in a production account.
Container images are built in layers. Each RUN, COPY, and ADD instruction creates a new layer. When a secret is added in one layer and deleted in a later layer, it is not removed from the image — it is only hidden from the final filesystem view. The layer containing the secret still exists in the image manifest and can be extracted:
docker save my-image | tar -x
# Each layer is a tar archive — extract and inspect
find . -name "*.env" -o -name "*.key" -o -name "credentials"
Tools like trufflehog, dive, and trivy can scan image history automatically.
Restrict metadata access:
For AWS, enforce IMDSv2 (token-required mode) and set hop limit to 1 on the instance. This prevents containers from reaching the metadata endpoint in most configurations:
aws ec2 modify-instance-metadata-options \
--instance-id i-xxx \
--http-tokens required \
--http-put-response-hop-limit 1
For containers that do not need instance metadata at all, block the endpoint via network policy or iptables.
Apply least-privilege to workload roles:
The IAM role attached to a container should have exactly the permissions the application needs — nothing more. If the app reads from one S3 bucket, the role policy should name that exact bucket and only allow s3:GetObject.
Scan image layers before pushing:
Make image scanning part of the CI pipeline. Any image with secrets in its history should fail the build, not just be flagged.
In cloud, the workload identity is often more valuable than root on the host. A container running as uid 1000 with no sudo and a read-only filesystem can still be the pivot point into a production account if the IAM role attached to it has been granted permissions that were never properly reviewed.
The question to ask about every workload is not "what user does this container run as?" but "what can the identity attached to this workload do — and what can it reach?"
Related: Case 02 — Container Metadata Abuse and IAM Escalation
Categoria: Cloud · IAM · Containers
Nível: Praticante
Existe um modelo mental persistente na segurança de infraestrutura que enquadra privilégio como algo binário: ou você tem root, ou não tem. Em ambientes cloud, esse modelo é perigosamente incompleto.
Um container rodando como usuário não-root, sem shell, sem ferramentas e com acesso limitado de leitura a arquivos, ainda pode ser o ponto de partida para um comprometimento total entre contas — se a identidade de workload associada a ele tiver as permissões erradas.
Cada grande provedor de cloud expõe um serviço de metadados em um endereço link-local (tipicamente 169.254.169.254). Esse endpoint é acessível por qualquer processo rodando no host ou dentro de um container, a menos que seja explicitamente bloqueado. Ele fornece:
Consultá-lo requer apenas uma requisição HTTP:
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Retorna: my-app-role
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/my-app-role
# Retorna: AccessKeyId, SecretAccessKey, Token, Expiration
Se uma vulnerabilidade na aplicação permite qualquer forma de requisição HTTP de saída ou leitura de arquivo, esse endpoint é alcançável. SSRF, path traversal, vulnerabilidades em dependências — qualquer um desses pode ser o ponto de entrada.
As credenciais retornadas são temporárias, mas totalmente funcionais — são credenciais AWS vinculadas à role IAM associada ao workload. O raio de blast depende inteiramente do que essa role tem permissão de fazer.
Em um exercício real, uma role que não conseguia listar todos os buckets, não conseguia criar instâncias e não tinha acesso administrativo óbvio ainda permitiu:
Isso foi suficiente. Uma imagem de container antiga no registry tinha um arquivo .env embutido em uma de suas camadas. O .env tinha credenciais para outro serviço. Esse serviço tinha uma relação de confiança cross-account. A cadeia terminou em uma conta de produção.
Imagens de container são construídas em camadas. Cada instrução RUN, COPY e ADD cria uma nova camada. Quando um segredo é adicionado em uma camada e deletado em uma camada posterior, ele não é removido da imagem — apenas fica oculto da visão final do filesystem. A camada contendo o segredo ainda existe no manifesto da imagem e pode ser extraída:
docker save my-image | tar -x
# Cada camada é um arquivo tar — extraia e inspecione
find . -name "*.env" -o -name "*.key" -o -name "credentials"
Ferramentas como trufflehog, dive e trivy conseguem varrer o histórico da imagem automaticamente.
Restringir o acesso aos metadados:
Para AWS, force o IMDSv2 (modo com token obrigatório) e defina o hop limit para 1 na instância. Isso impede que containers acessem o endpoint de metadados na maioria das configurações:
aws ec2 modify-instance-metadata-options \
--instance-id i-xxx \
--http-tokens required \
--http-put-response-hop-limit 1
Para containers que não precisam de metadados da instância, bloqueie o endpoint via network policy ou iptables.
Aplique least-privilege às roles de workload:
A role IAM associada a um container deve ter exatamente as permissões que a aplicação precisa — nada mais. Se o app lê de um bucket S3, a policy da role deve nomear exatamente aquele bucket e permitir apenas s3:GetObject.
Escaneie as camadas de imagem antes de fazer push:
Inclua o escaneamento de imagens no pipeline de CI. Qualquer imagem com segredos no histórico deve reprovar o build, não apenas ser sinalizada.
Em cloud, a identidade do workload frequentemente vale mais do que root no host. Um container rodando como uid 1000 sem sudo e com filesystem read-only ainda pode ser o ponto de pivot para uma conta de produção se a role IAM associada a ele tiver permissões que nunca foram revisadas adequadamente.
A pergunta a se fazer sobre cada workload não é "como qual usuário esse container roda?" mas "o que a identidade associada a esse workload pode fazer — e o que ela consegue alcançar?"
Relacionado: Case 02 — Container Metadata Abuse and IAM Escalation