Building Cloud Control-Plane Detections with Chokepoints

Building Cloud Control-Plane Detections with Chokepoints

The premise in one line: attacker tooling rotates constantly, but the control-plane moves don't. When someone lands in a cloud account with a stolen credential, the platform gives them a finite set of API calls to reach any given outcome. Those calls are the chokepoints. Anchor a detection to one and it survives the next tool rotation. Anchor it to a user agent or a tool signature and you're rewriting it next quarter.

I got the framing from iimp0ster's detection-chokepoints, which does this for endpoint under the tagline "TTPs evolve. Chokepoints don't." I ported it to the cloud control plane and turned it into a catalog: cloud-detection-chokepoints. Right now it's 58 chokepoints across AWS (CloudTrail), GCP (Cloud Audit Logs), and Azure (Activity Log), spanning ten ATT&CK tactics, one YAML file each, all on one schema.

This article covers the method, the evidence per cloud, the honest gaps, and the field caveats that cost me time. It doesn't reproduce the rule YAML. For that, and for the portable Sigma, go to the repo.

What a chokepoint actually is

A chokepoint is a technical prerequisite an attacker can't design around. It's the step that survives tool rotation.

I decide whether something qualifies with a single question:

If the attacker switches tools tomorrow, does the detection still fire?

If the detection keys on a user agent, a source IP, a session name, or a tool signature, all things the attacker controls, then it isn't a chokepoint, and it rots the moment the attacker recompiles. But if it keys on the invariant, like "a new default IAM policy version that grants Action:*," it fires no matter what wrote it.

Every entry in the catalog is built by pulling on that one thread. The framework behind it is six questions per technique, adapted from the upstream endpoint methodology:

  1. What is the technique, technically?
  2. What preconditions enable it?
  3. What does the attacker control? (user agent, IP, tool, timing, names)
  4. What can't the attacker control? This is the chokepoint: the mandatory operation plus the narrowing invariant.
  5. Can we observe it? Is the operation in the audit log, and does the discriminating field survive to the SIEM?
  6. What are all the variations? Every API path to the same outcome, so you cover the family and not one verb.

Question 4 is the whole game, and question 5 is where half of these detections die (more on that below). Question 6 is what keeps a chokepoint from being a single brittle rule: there are usually three or four ways to grant yourself admin, and if you only cover one the attacker just uses another.

The convergence argument

The reason this works at all is that different actors, with completely different tooling, keep landing on the same handful of calls to complete their objectives.

Kaspersky did the endpoint version of this analysis on eight major ransomware operations in 2022 and found they shared the same core kill chain: External Remote Services, command interpreters, WMI, and LSASS dumping in every group, shadow-copy deletion and service-stopping in seven of eight. The tools rotated. The requirements didn't.

The same collapse happens in the cloud, and the aggregate telemetry from independent vendors all tells one story: cloud intrusion is credential-driven, and post-access behavior funnels onto a small set of IAM, data, and compute APIs.

  • Valid-account abuse is the top cloud initial-access method, 35% of cloud incidents in H1 2024. New or unattributed cloud intrusions were up 26% year over year, and access-broker ads selling stolen credentials up 50%. CrowdStrike, 2025 Global Threat Report
  • 60% of AWS IAM users have an access key older than a year, and long-lived credentials are "the most common cause of publicly documented cloud security breaches." Datadog, State of Cloud Security 2024
  • Stolen credentials were 16% of all intrusions, the #2 initial vector behind exploits at 33%. Mandiant, M-Trends 2025
  • Across 2024, cloud IAM "impossible travel" alerts rose 116%, bulk cloud-storage object downloads 305%, cloud snapshot exports 45%, and total cloud alerts 388%. Palo Alto Unit 42, 2025 Cloud Security Alert Trends
  • Cloud attackers go from initial access to impact in ten minutes or less; one compromised account was used to spin up 500-plus cryptomining instances every 20 seconds. Sysdig, 2024 Global Threat Report
  • On GCP specifically, identity issues were the initial-access vector in 83% of cloud and SaaS incidents in H2 2025; data was targeted in 73% of incidents, and roughly 65% of compromised accounts were used for cryptomining. Google Cloud Threat Horizons

The through-line is always the same four beats: valid credentials in, IAM enumeration and manipulation, bulk data or snapshot exfiltration, resource-hijack provisioning. Those four beats are exactly the chokepoint clusters that follow.

AWS: the best-documented cloud

AWS has the richest public incident reporting, so it's where the "same calls every time" claim is easiest to prove. Here are the campaigns I mapped, spanning ransomware crews, cryptominers, access brokers, and email-abuse operators, each using different tooling (CloudShell, S3 Browser, Pacu-style scripts, Terraform, custom Lambda scanners, DockerHub miners) and each forced onto the same control-plane verbs.

Campaign / actorYearWhat they did in AWSSource
LUCR-3 / Scattered Spider2023CloudShell to scrape all Secrets Manager secrets; IAM backdoor users and keys; StopLogging/DeleteTrail; S3 theft via S3 BrowserPermiso
Scattered Spider2025S3 enumeration and exfil to adversary buckets (ListBuckets/ListObjects)CrowdStrike
SCARLETEEL 1.02023IMDSv1 role-cred theft; GetCallerIdentity/ListBuckets; StopLogging; ~1 TB S3 exfil; minerSysdig
SCARLETEEL 2.02023CreateUser/CreateAccessKey admin persistence; 42 mining instancesSysdig
AMBERSQUID2023CreateRole plus AttachRolePolicy (Admin); mining across ECS/SageMaker/CodeBuild/AmplifySysdig
EleKtra-Leak2023GitHub-exposed IAM keys used in ~4 minutes; RunInstances across regionsUnit 42
TeamTNT2021~70 AWS CLI enum calls: ListUsers/ListRoles/GetAccountAuthorizationDetails/ListBucketsUnit 42
Cryptomining (GuardDuty ETD)2025RunInstances+CreateAutoScalingGroup (GPU); CreateUser/CreateAccessKey/AttachUserPolicy for SESAWS Security Blog
Androxgh0st2024Scan exposed .env for AWS keys; create IAM users/policies; SMTP/SES abuseCISA/FBI AA24-016A
SES-pionage2023Exposed keys to GetSendQuota/ListIdentities to UpdateAccountSendingEnabled spamPermiso
Exposed-.env extortion2024110k domains scanned; CreateRole+AttachRolePolicy Admin; SES; S3 exfil, delete, ransomUnit 42
Cloud email takeover2025PutAccountDetails (SES sandbox escape) plus CreateEmailIdentity for phishingWiz
Honeytoken persistence case2025Leaked key hit by 5 IPs in 150 minutes; SES recon; CreateUser/CreateLoginProfile; GetFederationToken; Lambda persistenceDatadog
Codefinger2025S3 ransomware with SSE-C (x-amz-server-side-encryption-customer-algorithm); 7-day lifecycle-delete timerHalcyon
Invictus IR S3 case2024Exposed key to PutBucketVersioning=Suspended, bulk object delete, ransomInvictus IR

Line those up and the clusters draw themselves.

Orientation and enumeration. Nearly every one of these starts with GetCallerIdentity and an enumeration burst: ListUsersListRolesGetAccountAuthorizationDetailsListBuckets. TeamTNT alone fired around 70 enumeration calls. This is the chokepoint that fires earliest and on the widest range of intrusions, which is why it's one of the five I'd cover first.

Privilege escalation. There's a finite set of "grant myself admin" primitives, and the crews use all of them: AttachRolePolicy/AttachUserPolicy with an Admin policy (AMBERSQUID, the GuardDuty campaign, the extortion op), CreateUser/CreateAccessKey to mint a backdoor identity (LUCR-3, SCARLETEEL 2.0), and the two self-escalation moves, CreatePolicyVersion with a wildcard default version and inline PutRolePolicy/PutUserPolicy. Cover one of these and the attacker just routes through another, which is exactly why the catalog carries all of them.

Credential access. LUCR-3 scraped every secret in Secrets Manager via CloudShell in one pass. GetSecretValue by a human or unexpected principal is the chokepoint; the caveat is that service execution roles read secrets constantly, so this only works scoped to non-service principals.

Defense evasion. SCARLETEEL 1.0 and LUCR-3 both ran StopLogging. Trail tampering has a near-zero base rate, so any occurrence is worth a look. KMS key sabotage (ScheduleKeyDeletion/DisableKey/PutKeyPolicy) is the interesting one here: in AWS it's still PoC-only (the "RansomWhen" research), but the real-world version has already happened in Azure (Storm-0501, below). That's a chokepoint you want live before it arrives, not after.

Exfiltration and impact. Bulk S3 reads (SCARLETEEL moved about a terabyte), snapshot/AMI sharing to an external account, SES sending abuse for spam and phishing, cryptomining via RunInstances and CreateAutoScalingGroup, and two flavors of ransomware: Codefinger's novel SSE-C encryption-in-place, and the Invictus case's version-suspend-then-delete. The SSE-C move is worth calling out because it's a genuinely new 2025 TTP: the attacker encrypts your objects with a customer-provided key AWS never stores, so there's nothing to recover.

One honest note that this AWS reporting forced on me. The "Scattered Spider added a SAML provider in AWS" story gets repeated a lot. Read the CISA advisory and the federation abuse is at the Okta/Entra tenant level, not an AWS CreateSAMLProvider call. So that chokepoint is real (it's a low-base-rate, high-value primitive) but I label it CAPABILITY, not INCIDENT, because there's no verified AWS incident naming the API. More on that labeling discipline below.

GCP: identity in, Data Access off

GCP has less granular per-methodName casework in public reporting than AWS, partly because the big identity crews (Scattered Spider/UNC3944, LUCR-3) are documented mostly in AWS and Azure, and partly because chains like UNC4899's span GCP and AWS without the write-ups cleanly separating the clouds. Where the attribution is really AWS/Azure, I mark the GCP entry TRANSLATABLE rather than overclaim it.

That said, the aggregate picture from Google Threat Horizons is sharp. Identity is the door (83% of incidents in H2 2025). Data is the target (73%, with 28% showing extortion indicators). Compute is the payday (about 65% of compromised accounts used for mining, and one incident spun up 1,600-plus GPU VMs from a single compromised App Engine default service account). And anti-forensics is now standard: every major ransomware crew deletes logs, core dumps, and backups. Google also auto-disables detected leaked service-account keys by default now, which is exactly why catching key creation and impersonation at the source matters more.

The actor convergence maps cleanly onto the same clusters as AWS: service-account and non-human identity abuse (UNC4899's CI/CD SA token, the leaked-key crews), OAuth and federation trust abuse (UNC6395 via Salesloft Drift, UNC6426 via npm to OIDC), log and backup destruction across every ransomware crew, and compute hijack for mining.

But GCP has two structural facts that shape every detection here, and they're the most important thing in this whole section.

Fact one: Admin Activity is always on, Data Access is off by default. Only BigQuery data reads are on out of the box. That single default cleanly splits every GCP chokepoint into "observable now" and "blind until you enable it":

Observable now (Admin Activity)Blind by default (Data Access)
CreateServiceAccountKey, CreateServiceAccountGenerateAccessToken/SignBlob/SignJwt (impersonation)
SetIamPolicy grants, auditConfig changesAccessSecretVersion (secret reads)
resource create (compute/functions/run/build)testIamPermissions/getIamPolicy (recon)
logging sink and bucket deletes, firewall, org policyCloud Asset SearchAll*, list/get enumeration
compute instance create (mining), resource sharingstorage.objects.get (bulk exfil, T1530)

This is the direct GCP analogue of the AWS "S3 GetObject not ingested" gap. Impersonation, secret reads, recon, and object-read exfiltration are invisible until you turn on Data Access logging per service. If you're going to spend budget anywhere, the highest-leverage change is enabling DATA_READ/ADMIN_READ on iamcredentialssecretmanagercloudresourcemanagercloudasset, and storage.

Fact two: iam.serviceAccounts.actAs has no audit event of its own. It's a permission check at resource-creation time, not a logged call. So impersonation-via-attach is only visible through the host create event (compute, functions, Cloud Run, scheduler, Deployment Manager, Cloud Build) with the attached service account inspected. Worse, Deployment Manager and Cloud Build sidestep actAs entirely by running as a default Editor agent, so a low-privilege principal merely using those build rights is the finding. This is the GCP version of the AWS PassRole family, and it's genuinely awkward to detect because there's no single verb to key on.

Azure: Storm-0501 and the elevateAccess primitive

For Azure I scoped strictly to the resource and control-plane layer (Azure Activity Log). Entra ID identity is a separate log source and, honestly, the part most SOCs already cover well, so it's out of scope here.

The anchor real-world case is Storm-0501 (Microsoft MSTIC, 2025), which is a near-perfect walk through the Azure chokepoints in sequence. After reaching Global Admin and Owner, it harvested storage keys (listKeys), exfiltrated, then deleted backups, snapshots, and recovery-vault items and removed immutability locks, before encrypting cloud-side with an attacker-controlled key. That single campaign exercises the RBAC role-assignment chokepoint, the storage-key credential-theft chokepoint, the diagnostic-settings / log-destruction chokepoint, and the backup-recovery-destruction chokepoint, one after another.

The other high-value Azure primitive is elevateAccess: a Global Admin can flip a single toggle (AUTHORIZATION/ELEVATEACCESS/ACTION) and gain User Access Administrator over every subscription in the tenant. Near-zero base rate, tenant-wide blast radius. It's CAPABILITY-tier because I don't have a named public incident calling the exact API, but it's the kind of thing you want alerting on before you need it.

Azure has the same two structural facts as GCP, restated in Azure terms:

The read gap. Activity Log (control-plane) is always on. Role assignments, key regeneration, NSG changes, diagnostic-setting deletes, disk-export SAS, and run-command are all there. But Key Vault secret reads and storage blob reads are data-plane and require per-resource diagnostic settings routed to Log Analytics, off by default. Same gap as AWS and GCP, same blocked cluster.

The role-assignment logging gap. roleAssignments/write doesn't cleanly log the granted role or principal. The Activity Log records the caller's authorizing role (identity.authorization.evidence.role), the target resource GUID, and the caller, but the role actually being assigned lives in a request body that often isn't captured. So "who just got made Owner" needs request-body inspection, and the base rule fires on all role assignments. Worth knowing before you promise anyone that alert.

The read-log gap is the same story on all three clouds

If there's one thing to take from all of this, it's that the single most important structural fact is identical everywhere: write operations are logged by default, reads are not.

CloudAlways onOff by default
AWSCloudTrail management eventsS3 GetObject / data events
GCPAdmin Activity + System EventData Access logs (secretmanager, storage, iamcredentials, ...)
AzureActivity Log (control-plane)Resource / data-plane diagnostic logs (Key Vault, Storage)

Which means the same cluster is blind on every cloud: secret reads, bulk object-read exfiltration, service-account impersonation, and permission recon. Rather than quietly drop those chokepoints from the catalog, I kept them and flagged them [blind]. They're real, and detectable the moment you enable the read logging. But reads are the expensive volume, so turning them on is a scoping-and-cost conversation, not a checkbox. Scope to crown-jewel resources first.

This is also why the catalog leans on management-plane invariants wherever it can. The management calls are unavoidable and logged. Some data-plane activity is unavoidable but not logged, and a detection you can't feed is a detection you don't have.

The five that break most playbooks

You don't have to build all 58 to matter. If you walk a generic cloud account takeover from initial access to impact, most of the path routes through a small set of unavoidable stages: sign-in, orientation, credential looting, escalation, persistence, evasion, lateral movement, exfiltration, impact. Stages two, four, and five are unavoidable for anyone who wants durable admin, and that's where to invest first.

  1. The enumeration burst. Nearly every intrusion orients here before it escalates. TeamTNT's ~70 calls, SCARLETEEL's GetCallerIdentity, the extortion op's ListUsers. Count distinct enumeration ops per principal per window.
  2. The three privilege-escalation primitives. Managed-policy attach, policy-version self-escalation, and inline-policy self-escalation. This is the finite "grant myself admin" set. Cover all three or the attacker uses the one you skipped.
  3. The role-trust / external-principal persistence move. The highest-value persistence, because it survives every credential rotation your IR team performs. Rotating keys doesn't help if the attacker owns a trust relationship.

Cover those five and you break the chain of the overwhelming majority of cloud-takeover playbooks, because the attacker has nowhere else to route the technique.

The honesty tax

This is the part I care about most, because it's the part most detection repos skip.

It would be easy to slap "detects APT activity" on all 58 and call it authoritative. I wouldn't trust a repo like that, so I didn't build one. Every entry carries a validation tier that says exactly how strong the public evidence is:

  • INCIDENT: a named actor was documented performing this technique, in this cloud, calling this API.
  • AGGREGATE: vendor telemetry across many incidents quantifies the behavior class as prevalent, even if no single named incident calls the exact verb.
  • CAPABILITY: demonstrated feasible (PoC, research, or simulation), but not yet tied to a public attributed incident.
  • TRANSLATABLE: documented in another cloud, inferred here.

When I finished tagging everything, only 14 of the 58 came out INCIDENT. Most are CAPABILITY. I think that's the honest result, and I don't read it as a weakness. A CAPABILITY chokepoint is one you get to catch before it lands in someone's incident report. The tiering also tells you where the evidence is thin, so you can weight your own build order and not overclaim to your leadership.

A few of the honest gaps, so they're on the record:

  • AWS CreateSAMLProvider federation backdoor has no verified AWS incident. The famous Scattered Spider story is IdP-tenant-level, not an AWS API call. Kept as a real primitive, labeled CAPABILITY.
  • AWS KMS ransomware is PoC-only. The real key-destruction extortion has been seen in Azure (Storm-0501), not yet attributed in AWS.
  • Snapshot/AMI cross-account share for exfil shows up in aggregate telemetry (Unit 42 snapshot exports +45%) and simulation frameworks, but I found no attributed incident naming ModifySnapshotAttribute. Treated as AGGREGATE.
  • AssumeRole cross-account pivot is inferred from IAM-anomaly aggregates, not a clean named incident (the documented SCARLETEEL pivot used static keys, not AssumeRole).

Field caveats that cost me time

The useful part of any writeup is the stuff that didn't work the first time. These are the ones that bit me while building and validating the detections, and they generalize across clouds.

  • "Policy contains" versus "delta added." My first GCP privileged-grant rule matched roles/owner in the SetIamPolicy payload and fired on basically everything, because every policy contains roles/owner. You have to match the ADD binding delta, not the presence of the role. And even then, privileged-role ADDs are dominated by IaC and automation, so you allowlist automation principals from your own baseline. Same lesson on all three clouds.
  • The field-to-field comparison footgun. A rule like callerAccount != resourceAccount looks obvious, but a lot of query languages read fieldA != fieldB as "compare fieldA to the literal string fieldB," so it matches everything. Always validate the result count on real data before you trust the logic.
  • The discriminating signal often lives in the request body. AWS requestParameters, GCP bindingDeltas, Azure request body. Some SIEM parsers flatten these to fields, some leave them only in the raw event. Confirm your parser exposes the field your rule keys on, or the rule is a no-op.
  • Azure roleAssignments/write doesn't log the granted role or principal cleanly. Covered above, but it's the single most common Azure over-promise, so it's worth repeating.
  • Caller-IP redaction. Cloud-internal, service-to-service calls show internal or service addresses (AWS service names, GCP private/gce-internal-ip, Azure internal), so IP-based geo and anomaly logic can't see intra-cloud moves. Don't build a chokepoint on caller IP.
  • GCP field reliability. serviceData is deprecated and frequently dropped from exported logs, PERMISSION_DENIED calls by a user identity can lose principalEmail attribution, and MITRE v19 (April 2026) moved T1562 "Impair Defenses" to a new tactic. Validate every delta-based rule against a live sample, and re-map tactics before you hard-code them.

How the detections are structured

Every chokepoint carries detection logic at three tiers, borrowed from the upstream maturity model, so you can start hunting immediately and tighten toward a production alert:

TierFP ratePurpose
ResearchhighBaseline visibility. Every occurrence, minimal filter. This is your starting hunt query.
HuntmediumAdd the narrowing invariant and grouping.
AnalystlowProduction alert. Tightest invariant plus allowlists, carries severity.

The one rule I'd underline: don't ship the Analyst tier without baselining against your own data. The allowlists and thresholds in the repo are deliberate placeholders, because "which service accounts legitimately mint keys" is a question only your environment can answer. Portable Sigma rules for the buildable chokepoints are in the repo, and there are safe, lab-only emulation commands to fire each chokepoint's telemetry so you can confirm a rule fires before you trust it.

Using it

If you're doing detection engineering, lift the Sigma or just the invariant analysis, but baseline the allowlists on your own data. If you're hunting, the Research-tier logic is your starting query, and the attack chain shows which chokepoints to prioritize. If you're red-teaming, the emulation sheets give you one command per chokepoint to validate coverage. And if you spot a chokepoint I missed, and I know I missed some, there's a schema and a quick-add template to make adding one painless.

It's here: github.com/tresscross/cloud-detection-chokepoints.

GitHub - tresscross/cloud-detection-chokepoints
Contribute to tresscross/cloud-detection-chokepoints development by creating an account on GitHub.

The thing that keeps me coming back to this framing, from both sides of the keyboard, is that it ages well. Anchor a detection to the tool and you're rewriting it next quarter. Anchor it to the thing the attacker can't avoid, and it just keeps working. The tools will keep changing. These won't.

Sources

Threat reporting and vendor telemetry behind the chokepoints, grouped roughly by cloud. Where a figure came from a secondary source or couldn't be re-verified against a primary PDF, the repo's per-cloud convergence docs flag it.

Cross-cloud aggregate telemetry

AWS incidents

Azure incidents

Method and mapping