Quase nunca um principal IAM tem AdministratorAccess diretamente. Ele tem iam:PassRole e lambda:CreateFunction. Ou iam:CreatePolicyVersion em uma política anexada a ele mesmo. Ou consegue assumir uma role cuja trust policy ele pode reescrever. Nenhuma dessas políticas, lida sozinha, parece perigosa. O perigo está na combinação, entre principais e entre contas.

Este artigo apresenta o escalint, uma ferramenta open-source em Go que desenvolvi para encontrar essas combinações: ela lê um snapshot do IAM de uma ou mais contas AWS, avalia as políticas do jeito que a AWS avalia, monta um grafo de quem pode agir como quem e mostra, de forma visual e explicável, todos os caminhos que terminam em administrador, além do menor conjunto de mudanças que encontra para cortá-los.

Há uma demo ao vivo com dados sintéticos, que roda inteiramente no navegador.

O cenário atual

O lado ofensivo está muito bem servido

Quem ataca AWS tem ferramentas modernas e mantidas. O Pacu, da Rhino Security Labs, é um framework de exploração com módulos que enumeram permissões e executam a escalada. O CloudFox, da Bishop Fox, mapeia a superfície de ataque de uma conta a partir de credenciais comprometidas. O Stratus Red Team, da Datadog, emula técnicas de ataque em cloud mapeadas ao MITRE ATT&CK. Esse ecossistema acompanha cada novo serviço e cada nova técnica publicada.

O lado defensivo, nem tanto

Do lado de quem defende, o cenário de análise de acesso efetivo em IAM é bem mais fraco:

  1. Prowler: é o CSPM open source de referência, e com razão. Mas é amplo por natureza: centenas de checagens de configuração em vários provedores (S3 público, security groups abertos, criptografia, logging). As checagens de escalada olham política por política, procurando combinações de ações perigosas. Ele não responde "este principal, assumindo aquela role, consegue chegar a admin em dois passos?".
  2. PMapper: o projeto da NCC Group mostrou que IAM deve ser entendido como grafo e foi pioneiro nisso. Porém está sem atualização relevante há anos, e o catálogo de técnicas e serviços parou no tempo.
  3. CloudMapper e Cloudsplaining: o CloudMapper, da Duo Labs, parou em 2022. O Cloudsplaining, da Salesforce, gera bons relatórios de políticas excessivas, mas analisa cada política isoladamente e tem futuro incerto.

Em dezembro de 2025, a Datadog publicou o pathfinding.cloud, um catálogo aberto de caminhos de escalada de privilégio em AWS. No lançamento, dos 65 caminhos documentados, 27 (42%) não eram detectados por nenhuma ferramenta open source avaliada. Hoje o catálogo tem 92 caminhos. Ou seja: o atacante tem um framework que executa a técnica, e o defensor frequentemente não tem nada que a aponte antes.


Por que ver caminhos de forma visual

Existe uma frase conhecida de John Lambert, da Microsoft: "Defenders think in lists. Attackers think in graphs. As long as this is true, attackers win." O Active Directory passou exatamente por isso. O BloodHound nasceu como ferramenta ofensiva e acabou adotado por times de defesa, porque um grafo de caminhos de ataque mostra o que nenhuma lista de achados mostra.

IAM na AWS tem o mesmo formato. Uma lista diz "a role X tem iam:PassRole em *" (média severidade, perdida entre outras duzentas). Um grafo diz "o usuário de CI pode criar uma instância EC2 com a role Y, que pode anexar uma política a si mesma, que vira admin da conta". O primeiro é um achado; o segundo é um caminho, com começo, meio e fim, e com um lugar óbvio para cortar.

Ver o caminho também muda a correção. Quando dez caminhos passam pela mesma aresta, essa aresta é um choke point: removê-la resolve dez problemas de uma vez. Em uma lista, os dez aparecem como dez itens independentes.

Visão Trace do escalint: um caminho de dois passos até admin, com as ações exigidas, o ID do pathfinding.cloud e a visibilidade no CloudTrail de cada passo

Como funciona

 escalint collect (só leitura)  ou  aws iam get-account-authorization-details
                          │
                parsing estrito do snapshot ─────── dados não confiáveis, limites de tamanho
                          │
                avaliador de políticas IAM ──────── deny explícito, SCP, RCP, boundary,
                          │                          trust policy, cross-account; trace por decisão
                          ▼
               grafo "pode agir como" ◄──────────── 39 regras adicionam arestas de escalada
                          │
     ┌──────────┬─────────┼──────────┬──────────────┐
     ▼          ▼         ▼          ▼              ▼
  k caminhos  tiers   blast radius  choke points  plano de correção
  até Tier 0                                      (guloso)
                          │
     analysis.json ──► texto · JSON · SARIF · DOT · Mermaid · UI web embutida

Avaliação fiel

O núcleo é um avaliador que segue a lógica documentada da AWS: deny explícito em qualquer política, SCPs e RCPs por nível de OU, permissions boundaries, políticas de identidade, trust policies e regras cross-account, incluindo NotAction, NotResource, NotPrincipal e wildcards em ações e ARNs. Cada Allow vem com um trace dizendo qual statement decidiu. Condições que não podem ser avaliadas estaticamente (MFA, IP de origem, horário) não são descartadas: o passo vira conditional e aparece tracejado no grafo.

O grafo e as regras

Os nós são principais (usuários, roles, grupos) e um sumidouro por conta, o Tier 0 (admin efetivo). As arestas vêm de 39 regras: 30 caminhos de escalada (EL-PE), 6 erros de trust (EL-TR, por exemplo OIDC do GitHub Actions sem condição em sub) e 3 diagnósticos de entrada (EL-PARSE). Toda regra cita a documentação da AWS, o ID do pathfinding.cloud e as técnicas do MITRE ATT&CK correspondentes. A versão atual cobre 33 dos 92 caminhos do catálogo.

Análises

Sobre o grafo, o escalint calcula os k menores caminhos de cada principal até o Tier 0, tiers de privilégio, raio de impacto (blast radius), ciclos de assume-role, choke points e um plano de correção guloso: a sequência de mudanças, ordenada, que remove todos os caminhos. Para cada passo, ele também diz quais eventos o CloudTrail registraria, ou se aquele passo é um ponto cego.


A interface

A UI é um app React em TypeScript estrito, com renderer WebGL próprio, embutido no binário. Ela roda local com escalint serve (só em 127.0.0.1, com token de uso único) ou como um único arquivo HTML offline com escalint report --html. São nove visões:

  1. Overview: tiers, caminhos críticos, pontos cegos do CloudTrail e o que corrigir primeiro.
  2. Atlas: todos os principais da conta (ou da organização), agrupados por conta, com zoom semântico; Shift+clique em dois nós mostra o menor caminho entre eles.
  3. Trace: um caminho passo a passo, com alternativas e choke points. Clicar em uma aresta abre o explicador de política, com o statement exato que permitiu aquele passo.
  4. Reach: o que um principal alcança, e quem o alcança, em anéis por número de saltos.
  5. Exposure, Findings, Fix plan, Detect e Diff: matriz principal × serviço, tabela filtrável, plano de correção com prévia ao vivo dos caminhos restantes, eventos do CloudTrail por passo e o que mudou entre dois snapshots.
Visão Atlas do escalint: todos os principais da conta em um grafo WebGL

Todo grafo tem uma tabela equivalente por trás, a navegação funciona pelo teclado e as paletas são seguras para daltonismo. Como o snapshot vem de uma conta que pode ter nomes e políticas hostis, a UI usa CSP estrita e nunca usa innerHTML, e --redact troca todo identificador por um pseudônimo antes de compartilhar um relatório.

Visão Fix plan do escalint: mudanças ordenadas e os caminhos que restam após cada uma

Hands-on

Os binários de release (Linux, macOS e Windows, amd64 e arm64) já trazem a UI embutida e são assinados com cosign, com atestado de proveniência do GitHub. Também há uma imagem distroless:

# escolha a plataforma em https://github.com/brennocm/escalint/releases
curl -sSLO https://github.com/brennocm/escalint/releases/download/v0.1.0/escalint_0.1.0_linux_amd64.tar.gz
gh attestation verify escalint_0.1.0_linux_amd64.tar.gz --repo brennocm/escalint
tar xzf escalint_0.1.0_linux_amd64.tar.gz escalint

docker run --rm -v "$PWD:/work" ghcr.io/brennocm/escalint:v0.1.0 scan /work/snapshot.json

O fluxo básico são três comandos. O collect só faz chamadas de leitura (List* e Get*), e o repositório publica a política IAM mínima para ele:

escalint collect -o snapshot.json   # snapshot só leitura com suas credenciais AWS
escalint serve snapshot.json        # abre a UI em 127.0.0.1 com token de uso único
escalint scan snapshot.json         # achados no terminal; exit 1 se houver high ou critical

Sem credenciais à mão, dá para usar as contas de exemplo do repositório. A saída real (resumida) do scan na conta vulnerável:

escalint scan examples/vulnerable-account/snapshot.json
1 account(s), 66 node(s), 114 edge(s), 15 path(s) to Tier 0; 19 critical, 28 high, 57 medium, 2 info

arn:aws:iam::111111111111:role/pe002-attacker (role, tier 1)
  path p_a9433adab2c37415 [CRITICAL, allowed, 1 step(s)]
    1. arn:aws:iam::111111111111:role/pe002-attacker --[modify_permissions EL-PE-002]--> effective admin (111111111111)
  CRITICAL EL-PE-002 Put an inline policy on its own role
    arn:aws:iam::111111111111:role/pe002-attacker can act as effective admin of account 111111111111 using iam:PutRolePolicy
    policy: inline:arn:aws:iam::111111111111:role/pe002-attacker/p statement #0 (snapshot line 766)
    ATT&CK: T1098.003 Account Manipulation: Additional Cloud Roles
    fix: Remove iam:PutRolePolicy from the role's policies, or scope its Resource to roles other than itself
         and add a permissions boundary that denies it.

arn:aws:iam::111111111111:role/tr002-ci-deploy (role, tier 3)
  CRITICAL EL-TR-002 GitHub Actions OIDC trust without a sub condition
    any GitHub Actions workflow, in any repository, can assume arn:aws:iam::111111111111:role/tr002-ci-deploy:
    the trust has no condition on token.actions.githubusercontent.com:sub
    policy: trust:arn:aws:iam::111111111111:role/tr002-ci-deploy statement #0 (snapshot line 1170)
    ATT&CK: T1199 Trusted Relationship; T1078.004 Valid Accounts: Cloud Accounts

Cada achado aponta a política e a linha do snapshot que o causam, as técnicas ATT&CK e uma correção concreta. Os códigos de saída permitem usar o escalint como gate: 0 sem achados acima de --fail-on (padrão high), 1 com achados e 2 erro de uso.

Em CI

O caso de uso que mais me interessa é impedir que um caminho novo apareça sem ninguém perceber. O escalint diff compara dois snapshots e falha apenas com arestas, caminhos e achados adicionados. O repositório traz uma GitHub Action que, em um job diário, coleta com uma role só leitura via OIDC, compara com o snapshot de ontem e envia o resultado em SARIF para o GitHub Code Scanning:

escalint diff baseline.json snapshot.json --fail-on high
escalint scan snapshot.json --format sarif --output escalint.sarif

Onde o escalint se encaixa

O escalint não substitui nenhuma das ferramentas citadas. O Prowler continua sendo o lugar para postura geral da conta; o escalint deliberadamente não olha bucket público nem security group aberto. O Pacu e o CloudFox continuam sendo o lugar para exploração e validação ofensiva; o escalint não assume roles, não cria credenciais e não altera nada na conta.

O espaço que ele ocupa é o do PMapper: análise defensiva, estática e explicável de acesso efetivo em IAM, agora com SCPs e RCPs, catálogo atualizado e mapeado ao pathfinding.cloud, saída pronta para CI e uma interface pensada para que o defensor enxergue o grafo que o atacante já enxerga. A ideia do grafo self-hosted, em que nenhum dado sai da conta, veio do aws-iam-graph, de marcelogsantos; o escalint é uma reimplementação do zero.


Limitações

A v1 não modela session policies e session tags, políticas baseadas em recurso além da trust policy (bucket policies do S3, key policies do KMS, Lambda, Secrets Manager), usuários root nem atribuições do IAM Identity Center. Condições de requisição não são conhecidas estaticamente, então os passos afetados ficam como conditional. A cobertura é de 33 dos 92 caminhos do pathfinding.cloud; a maioria dos restantes passa uma role para outro serviço de computação e segue o mesmo molde das regras existentes. O inventário de recursos é regional, o plano de correção é guloso (pequeno, mas não garantidamente mínimo) e um snapshot é uma fotografia: depois de mudanças, é preciso coletar de novo.

E um cuidado importante: o snapshot e os relatórios são, literalmente, um mapa de quem consegue virar administrador. Devem ser tratados como dado sensível e compartilhados só com --redact.


Considerações finais

Escalada de privilégio em IAM raramente é uma política obviamente errada. É uma soma de permissões razoáveis, espalhadas por principais e contas diferentes, que ninguém revisou juntas. O atacante tem ferramentas para encontrar e percorrer essa soma; o defensor, na maioria das vezes, tem uma lista.

O escalint tenta equilibrar isso: avaliar as políticas como a AWS avalia, desenhar o grafo, explicar cada aresta com o statement que a permite e dizer onde cortar. Não é garantia de que uma conta está segura, mas é uma forma de olhar para o IAM do mesmo jeito que quem vai atacá-lo.