Attacking Cloud Service Providers ACSP Chapter 11
Part IV · Chapter 11

Cross-Tenant Isolation & Provider-Side Vulnerabilities

After this chapter you will be able to look at any cloud vulnerability and name exactly which isolation boundary it broke, count how many tenants sit inside its blast radius, and write the severity rationale a provider's security team would actually defend.

~4,200 words · 4 figures

Imagine two locked doors in an apartment building. Behind the first is one tenant's storage closet — if the lock fails, that one tenant loses their stuff. Behind the second is the room where every tenant's keys hang on a board — if that lock fails, the whole building is open.

Both doors had the same cheap lock. The danger was never the lock. It was what the lock was protecting, and who else stood to lose if it gave way. This chapter is about learning to see — instantly, for any cloud bug — which door you are looking at.

The problem: the isolation boundary is the target

For ten chapters you have collected primitives — SSRF into a metadata service, a container escape, a confused deputy, a forged SAML assertion, a predictable bucket name. This chapter does not add an eleventh. It asks the question every previous chapter's final pivot was building toward: so what?

A public cloud is, at bottom, a stack of walls. Tenants share physical hosts, networks, control-plane services and global namespaces, and a set of isolation boundaries is the only thing keeping one tenant's compute, data and identity from touching another's. Every cross-tenant vulnerability is, by definition, one of those walls that an attacker reached and defeated. The bug is just the crowbar.

ℹ Note · The boundary is the target, not the backdrop

Until now we treated an isolation boundary as context — the architecture an attack happens to traverse. Promote it: a boundary is the thing you are attacking. When you read a vulnerability writeup, the first question is no longer "what's the bug" — it is "which wall fell, and what was on the other side."

Here is the whole argument in one example. A Postgres extension on a managed database fails to validate a file path, so an attacker reads files off the host — a real bug, patched, roughly one tenant affected because each managed database sits on its own host. Now take the same bug class — a missing check — and put it in Oracle's block-volume service, which forgets to verify that you own a disk before letting you attach it to your VM. A disk is named by an OCID, OCIDs are not secret, and the missing check is the only thing between your VM and every unattached disk in the region. Same mistake. Wildly different catastrophe. The difference was never the bug — it was what the broken component was shared with.

Why it matters: how this differs from a traditional pentest

A traditional pentest answers "can an attacker get in, and how far inside this customer's estate." Severity is scoped to one organisation. When you attack the provider, the unit of impact is no longer one organisation — it is the set of tenants, and in the worst cases the provider's own fabric. A finding's severity stops being a property of the bug and becomes a property of the architecture around it.

That changes the job in three concrete ways. First, the same exploit can be a footnote or a planetary incident depending on the sharing scope of the component it lands in — so you must measure that scope, not eyeball it. Second, CVSS, built to score a bug against a single system, systematically under-prices isolation breaks; you will write a rationale instead of trusting a number. Third, many provider-side bugs are invisible to the victim — the attack logs in the attacker's tenant or nowhere at all — so detection difficulty itself becomes part of the severity argument.

By the end of this chapter you should be fluent in two sentences for any cloud vulnerability you ever meet:

  1. "This break failed the ____ boundary."
  2. "Its blast radius is ____, because the failed component is shared at the ____ scope."

This is also where the six-part lens from Chapter 1 — plane · isolation boundary · identity propagation · shared component · provider "magic" · detection surface — stops being a teaching checklist and becomes a diagnostic instrument. You point it at a case and it returns the case's class, its severity, and its remediation.

The methods at a glance

This chapter is built on two axes. The first is a taxonomy of failed boundaries — every cross-tenant break files under exactly one primary boundary it defeated:

Failed boundaryWhat enforces itWhat a break grants
NetworkVNet/VPC segmentation, firewalls, host↔guest channels, service tagsReach another tenant's data plane or the provider's internal network
IdentityIAM/Entra trust policy, token validation, signed-token issuance, first-party principalsAct as another tenant or as the provider
Hypervisor / kernelVM isolation, container runtime, kernel namespacesEscape onto a shared host
NamespaceKubernetes namespaces, process/mount namespacesReach co-tenant workloads on a shared node or cluster
Account / subscription / tenancyThe top-level tenant boundary; per-resource ownership checksReach resources owned by another tenant directly
NamingGlobal namespace uniqueness, name allocation, ID unpredictabilityHijack or pre-claim a name another tenant relies on

The second axis is orthogonal: cross-tenant versus provider-side. A cross-tenant bug lets attacker-tenant A reach a named victim-tenant B. A provider-side bug is worse — it sits in a component the provider operates for all tenants at once, so a single exploitation is simultaneously an attack on everyone. A bug can be one, the other, or both.

The breakdown sections that follow take these two axes and the two skills they demand — blast-radius reasoning and severity reasoning — one at a time. Three flagship cases illustrate the technique steps: AttachMe (account boundary), the Entra ID Actor token (identity boundary), and Azure API Connections (the shared-component story).

Breakdown 1: classifying any break by its failed boundary

Every cross-tenant vulnerability in the corpus files under exactly one primary failed boundary. Most real attacks chain through several — but you classify by the boundary whose failure delivered the cross-tenant impact.

Figure 11.1The six isolation boundaries, what enforces each, and a flagship corpus exemplar per row. The chapter's reference figure — you should be able to point at it and classify any case you meet.

Four teaching beats turn the table into a reflex.

One — classify by the boundary that delivered cross-tenant impact, not the first one the chain touched. ChaosDB — the Azure Cosmos DB break you walked end-to-end in Chapter 8 — chains namespace (a payload escapes the Jupyter notebook container to the host), then network (iptables -F clears the in-container firewall and exposes the host's metadata IPs), and finally account/subscription: one regional Service Fabric cluster managed 500+ Cosmos DB instances belonging to different subscriptions behind a single shared certificate.[1]#056 File ChaosDB under account, because that is where another tenant's data appeared. The namespace and network legs were the means; the account boundary was the breach.

Two — identity is the most dangerous category, because an identity break needs no further pivot. With every other boundary you escape toward the victim and then still have to act. With an identity break you simply are the victim. The Entra Actor token (Breakdown 4) involves no container escape and no lateral movement: you authenticate as the victim's Global Admin and the directory does what Global Admins do. Azurescape (#054, Chapter 6) classifies under identity for the same reason — the runC escape was only the means, and a privileged Kubernetes service-account token reachable inside the tenant was the actual break.[8]#054

Three — the naming boundary is the one web-security engineers under-rate. It has no firewall and no policy engine — just an assumption that a string is unguessable. When that assumption is wrong, there is nothing else to defeat. Predictable OCIDs are the entire reason AttachMe (Breakdown 3) was catastrophic in practice rather than theoretical.

Four — enforcement and attack surface are often the same endpoint. In ChaosDB the component you are allowed to talk to (the Jupyter kernel) is the same component meant to keep you contained. In OMI it is the management socket; in API Connections it is the APIM instance. When the thing you can reach is the thing that enforces isolation, every parsing quirk in it — a header it mishandles, a path it normalises, a token it fails to scope — is an isolation bug.

◈ Concept · Provider-side vulnerability

A cross-tenant bug lets attacker-tenant A reach victim-tenant B — one wall fell between two named tenants. A provider-side bug sits in a component the provider operates for all tenants at once, so there is no wall to fall: a single exploitation is simultaneously an attack on every tenant that depends on the component. The distinction tells you the order of magnitude of the blast radius before you measure anything else.

Place the corpus on a 2×2 of these two axes and the categories sharpen:

Not provider-sideProvider-side
Cross-tenantAppSync confused deputy (#074[9]) — attacker account assumes a role in one victim account at a time.ChaosDB (#056), API Connections (#197) — one credential or one shared instance reaches every tenant.
Not cross-tenant (per se)A single-host bug like log_fdw (#015) — affects only the attacker's own host.OMI/OMIGOD (#057, #087), RHUI (#068) — every tenant is affected, but via the shared agent or infrastructure rather than an "A→B" reach.

OMI and RHUI sit in the bottom-right and are worth pausing on: they are not "A reaches B" attacks at all, yet they are among the highest-severity entries in the corpus, because the broken component lives on every tenant's machines. Two specific shapes of provider-side surface recur often enough to deserve names.

◈ Concept · First-party service principal

A first-party service principal is the cloud provider's own identity, pre-installed inside your tenant — Exchange Online is 00000002-0000-0000-c000-000000000000 in every Entra tenant; every AWS account trusts service principals like glue.amazonaws.com. You did not create these identities and usually cannot remove them. They are a confused deputy by construction (Chapter 3): a highly trusted identity the provider injected, and abusing one is exploiting trust the tenant never granted.

◈ Concept · Cloud middleware / host agent

Cloud middleware — Wiz's term — is software the provider silently installs on customer VMs to bridge them to managed services. It runs as root, is undocumented, deploys automatically the moment a customer enables an unrelated feature, and sits outside the customer's patch awareness. A single bug in one such agent is therefore an every-tenant bug — the deployment model decides the blast radius before the bug is even written.

⚠ Pitfall · The shared-responsibility gap

The shared-responsibility model says the provider secures the cloud and the customer secures what they put in it. Cloud middleware tears a hole in it exactly its own size: when the provider installs an agent the customer does not know exists, neither party clearly owns the patch. That gap is why OMIGOD lingered unpatched on so many machines for so long.

Breakdown 2: blast-radius reasoning

The taxonomy tells you which wall fell. The genuinely new skill is measuring how far the break reaches. Severity is not vibes and it is not CVSS — it is a quantity you compute and then defend.

◈ Concept · Blast radius

Blast radius is the set of tenants — or provider systems — an attacker can reach once the bug is exploited, before any further bug is needed. It is a formal, defensible quantity, and crucially it is a property of what the broken component is shared with, not of the bug's cleverness. A brilliant exploit in a per-host agent has a blast radius of one; a trivial exploit in a global control plane has a blast radius of everyone.

Compute it as a five-step procedure:

Procedure · computing a blast radius
  1. Identify the component the bug lives in — not the symptom, the actual code, service or agent.
  2. Ask at what scope that component is shared — per-VM agent · per-node · per-region cluster · per-cloud control plane · global. This one answer is the order of magnitude of the blast radius.
  3. Multiply by the access the bug grants — read vs read-write vs code-execution vs identity-takeover vs control-plane.
  4. Subtract any architectural containment — node-per-tenant, disabled RuntimeAccess, dedicated hosts. This is where defenders win, and where two structurally identical bugs diverge.
  5. State the result as a set of tenants or systems — "every OCI tenancy in scope," "every Linux VM running an Azure management feature" — never as a score.
Figure 11.2The same bug — a missing authorization check — at three sharing scopes. The bug is constant; the architecture multiplies it from one tenant to a region to the planet.

Run the procedure on ChaosDB. Component: the regional Service Fabric cluster certificate. Sharing scope: the cluster managed 500+ Cosmos DB instances across many subscriptions, and the same class of certificate authenticated 100+ internet-facing clusters across multiple Azure regions — per-region-cluster, escalating to multi-region. Access: full read-write of every database's data and keys. Containment: none — the stolen certificates kept working over the public internet even after Microsoft disabled the vulnerable notebook feature. Result: several thousand tenants, including Fortune 500 companies. That is the moment a cross-tenant bug becomes a provider-side bug.

Now run it on log_fdw (#015), a path-traversal file read in an RDS PostgreSQL extension.[10]#015 Component: a Postgres extension on one managed RDS host. Sharing scope: per-host — every managed database has its own host. Access: file read on the local host. Containment: the host is single-tenant; the boundary holds. Result: one tenant — the attacker's own. The bug class is identical to AttachMe's; the blast radius differs by a factor of millions.

ℹ Note · Pre-auth vs post-auth blast radius

Ask whether the attacker needs any foothold. A pre-authentication bug is reachable with no valid account; a post-authentication bug needs some foothold — a free tenant, a stolen token, a paid service. This feeds the "exploitation cost" factor of a severity rationale, and a free, trivially obtained foothold barely raises that cost above zero.

Breakdown 3: AttachMe — one missing check, every tenant

AttachMe (#086) is the chapter's purest specimen of the account / tenancy boundary, and the payoff to the cold open: it demonstrates, more cleanly than any other corpus case, that severity lives in the architecture and not in the bug.

Oracle Cloud Infrastructure (OCI) Block Volume provisions virtual disks — boot volumes and data volumes — that attach to compute instances. A volume is identified by an OCID, OCI's universal resource identifier. OCIDs are not secrets: they appear in URLs, IaC files and CI/CD logs, and are routinely indexed by code search engines. The volume-attach API required the caller to hold the VOLUME_WRITE permission — but it never checked that the caller owned the volume being attached, or that the volume lived in the caller's tenancy at all. A permission check without an ownership check.

Technique steps · cross-tenant OCI volume access (#086)
  1. Harvest a victim volume OCID from public code search or GitHub — the "secret" was never secret.
  2. Confirm the OCID is foreign: reading its metadata is correctly rejected — oci bv volume get --volume-id ocid1.volume... returns NotAuthorizedOrNotFound (HTTP 404).
  3. Stand up an attacker instance in the same Availability Domain as the target — at most three per region, all enumerable.
  4. Attach the same foreign volume to that instance: oci compute volume-attachment attach --instance-id <attacker> --volume-id <victim-volume> --type paravirtualized — and it succeeds, returning a live attachment in lifecycle-state: ATTACHING.
  5. The victim's disk is now a read-write block device on the attacker's VM: read their data and backups; write a boot volume to plant code that runs the next time the victim boots.
# Step 2 — reading a volume you don't own is correctly rejected
$ oci bv volume get --volume-id ocid1.volume.oc1.phx.<victim-volume>
ServiceError: NotAuthorizedOrNotFound (HTTP 404)

# Step 4 — attaching the very same volume succeeds
$ oci compute volume-attachment attach \
    --instance-id ocid1.instance.oc1.phx.<attacker-instance> \
    --volume-id  ocid1.volume.oc1.phx.<victim-volume> \
    --type paravirtualized
{ "data": { "lifecycle-state": "ATTACHING", ... } }

All three preconditions are trivially met — a public OCID, an instance in one of three Availability Domains, and a volume that is detached or shareable (detached boot volumes survive instance termination by default, so they accumulate). Oracle patched within 24 hours and instructed no customer to take action, because there was no customer-side action to take.[2]#086

A code search result showing OCI volume OCIDs embedded in a public source file
Figure 11.3Volume OCIDs leaking through public code search — the "secret" that AttachMe needed was not secret at all. The naming boundary fails first. Source: [2]#086

Classify it. Primary failed boundary: account / tenancy — the bug reaches resources owned by another tenant directly. The naming boundary is the enabler: predictable, non-secret OCIDs turned a theoretical missing check into a region-wide attack. And it is squarely provider-side — the volume-attach API is a core OCI service operated for every customer at once. One missing if statement; blast radius, every OCI customer.

Breakdown 4: the Entra Actor token — Global Admin everywhere

If AttachMe is the purest specimen, the Entra ID Actor token (#246, CVE-2025-55241) is the most severe — the identity-boundary flagship and the textbook example of a first-party service principal abused.

Figure 11.4The Entra Actor token chain. The Exchange Online service principal (purple) is a first-party identity that exists, identically, inside every Entra tenant — so the attacker manipulates it in their own tenant.

Entra's Access Control Service issues a backend, service-to-service token called an Actor token. It is signed by Entra, valid for 24 hours, and carries the claim trustedfordelegation: true. The legacy Azure AD Graph API, which consumes these tokens, never checked that the tenant named in an impersonation request matched the tenant the Actor token originated in — it only checked that a user with the requested identifier existed in the target tenant.

Technique steps · cross-tenant Global Admin (#246, CVE-2025-55241)
  1. In your own tenant, add a credential to the Exchange Online service principal — a first-party principal you cannot remove but can touch.
  2. Request an Actor token for resource 00000002-0000-0000-c000-000000000000, the legacy Azure AD Graph API. This is a fully legitimate, signed, Entra-issued token — and requesting it logs nothing.
  3. Recover a victim user's netId — these are incremental, not random, so they are brute-forceable and also leak from B2B guest objects' alternativeSecurityIds.
  4. Craft an unsigned wrapper JWT (alg: none) embedding the signed Actor token and the victim's identifier (see below).
  5. Send it to Azure AD Graph against the victim tenant. The missing tenant-consistency check means the Actor token minted in your tenant now impersonates any user — including Global Admins — in any tenant.
{
  "actortoken": "<the signed, Entra-issued Actor token>",
  "nameid": "<victim user's netId>",
  "aud": "00000002-0000-0000-c000-000000000000/graph.windows.net@<victim-tenant-id>"
}

The break is invisible to the victim. Requesting the Actor token logs nothing; the wrapper JWT is built entirely client-side, so Entra in the victim tenant never participates and emits no sign-in log; Azure AD Graph has essentially no API-level logging; and Conditional Access applies to user sign-ins, not service-to-service tokens, so it never fires. The one artifact: a data-modifying action logs the impersonated Global Admin's UPN against the calling application's display name — "Office 365 Exchange Online." A human Global Admin acting as an Exchange application is a contradiction, and Microsoft later turned exactly that contradiction into a detection signature.[4]#246

Classify it. Primary failed boundary: identity — and notice there is no other leg. No container escape, no network pivot, no lateral movement. The bug delivers cross-tenant impact by itself, which is precisely why identity is the most dangerous category. It is provider-side: the Azure AD Graph tenant-validation logic is a single global control-plane component. And it is the corpus's clearest first-party-principal abuse — the attacker's lever is Microsoft's own Exchange identity, pre-installed in every tenant including the attacker's.

Breakdown 5: API Connections — the wall that was never there

API Connections (#197) is the cleanest "shared component = all-tenant blast radius" story in the corpus — a 2025 case, and a perfect illustration of a provider-side bug that is also cross-tenant.

Figure 11.5The API Connections chain. Every connection ever created — for every tenant — lives on one shared APIM instance, and ARM calls it with a token scoped to all connections.

API Connections back Logic Apps' integrations — Key Vault, Azure SQL, Salesforce, Jira, Slack. The structurally fatal fact: every API Connection, for every tenant, is created on a single, globally shared Azure API Management (APIM) instance, and Azure Resource Manager (ARM) holds a super-privileged token with access to every connection on that instance. A direct token-exchange against another tenant's connection returns 403 — a per-connection ACL is enforced — so the attacker never touches that path.

Technique steps · worldwide cross-tenant compromise (#197)
  1. Obtain the victim's ConnectionId — not a secret; it routinely leaks in CI/CD logs and IaC.
  2. On your own connection, invoke the DynamicInvoke endpoint, which lets a Contributor supply a request path.
  3. Define a Logic App Custom Connector with a free-string {path} parameter.
  4. Supply, as that parameter, a traversal payload: {path} = ../../../../<VictimConnectorType>/<VictimConnectionID>/<action>.
  5. ARM appends the path and calls the shared APIM instance with its all-connections token; the ../../../../ normalises the URL onto the victim's connection, and the per-connection ACL is never re-checked because ARM trusts the caller-supplied path. Result: full read of any cross-tenant Key Vault, Azure SQL, Salesforce, Jira or Slack backend, worldwide.
POST /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Web/connections/keyvault/DynamicInvoke?api-version=2018-07-01-preview

{ "request": { "method": "get", "path": "/secrets" } }
A captured API Connection definition showing the DynamicInvoke ARM endpoint and the connection runtime URL on the shared APIM host
Figure 11.6The connection definition exposes the DynamicInvoke ARM endpoint and a connectionRuntimeUrl on the shared azure-apihub.net APIM host — the shared component sits in plain sight. Source: [5]#197

Classify it. Primary boundary: account / subscription — the traversal walks straight to another tenant's owned connection. It is both cross-tenant (A reaches a specific B named by a ConnectionId) and provider-side (the shared APIM instance plus the all-connections token mean one exploitation can reach every tenant).[5]#197

⚠ Pitfall · The fix was a blacklist

Microsoft's fix disallowed ../ in the path parameter — a point patch on one traversal encoding of a structural flaw, and blacklists invite encoding bypasses (..%2f, double-encoding, unicode). The architectural fix removes the shared-blast-radius condition: scope the ARM token per-connection so traversal, even if achieved, cannot cross a tenant boundary. Point patches make a bug harder; architectural fixes make the class of bug contained.

Breakdown 6: the cloud-middleware specimens — OMI and RHUI

Two more cases nail down the cloud-middleware concept. Treat them briefly — the point is the deployment model, not the bug.

OMI / OMIGOD (#057, #087). OMI — Open Management Infrastructure — is the canonical cloud-middleware specimen: a root-running, undocumented agent silently installed by Log Analytics, Azure Diagnostics, Security Center, Automation and more. Wiz found it on more than 65% of sampled Azure customers. CVE-2021-38647, "OMIGOD," is almost insultingly simple: remove the Authorization header and you are root. A valid WS-Man SOAP ExecuteShellCommand request carries Authorization: Basic ...; the same request with no auth header at all returns 200 OK and runs the command as uid=0(root). The root cause is one line of C: the Http_SR_SocketData struct is memset to zero, so AuthInfo{uid,gid} defaults to {0,0}; with no Authorization header, neither the re-auth path nor the auth-failure path triggers, and the request runs as root. The patch added two lines — initialise those IDs to INVALID_ID.[3]#057#087

A source diff adding authInfo.uid and authInfo.gid initialisation to INVALID_ID
Figure 11.7The OMIGOD fix: two lines initialising authInfo.uid/gid to INVALID_ID instead of leaving them zeroed at root. A one-line bug; an every-Linux-VM blast radius. Source: [3]#057#087

The follow-up #087 (CVE-2022-29149) is a teaching point in its own right: the patched code generated a secretString with srand(time(&t)) — seeded on process-start time, therefore predictable — so the fix itself was bypassable. The lesson is not the bug. It is that OMI's deployment model — auto-installed on every Linux VM that touches an Azure management feature — makes a single bug an every-Linux-VM bug, and the shared-responsibility gap means nobody clearly owned the patch.[3]#057#087

RHUI (#068). Azure's Red Hat Update Infrastructure is a different flavour of all-tenant blast radius: not "read every tenant's data" but "push code to every tenant's machines." Every Azure-marketplace RHEL VM trusted a provider-run yum repository. RPM metadata on a marketplace RHEL box (rpm -qip) leaked the build host rhui-monitor.cloudapp.net; an exposed log-collector on port 8080 returned archives containing an admin SSL certificate for the Red Hat Update Appliances; and every client's repo config shipped with gpgcheck=0. An attacker with appliance admin could upload unsigned malicious packages that every Azure RHEL VM would pull on its next yum update.[6]#068 Provider-side, all-tenant, and a software-supply-chain primitive — the naming/supply-chain row of the taxonomy, at provider scale.

Breakdown 7: severity reasoning — the rationale, not the score

You can now classify a break and measure its blast radius. The last skill is turning those into a severity statement a provider's security leadership would read and sign.

◈ Concept · Provider-grade severity rationale

A provider-grade severity rationale is not a number — it is a paragraph covering five factors: blast radius (how many tenants, from the procedure above), access gained (read / write / code-execution / identity-takeover / control-plane), exploitation cost (pre-auth vs needs-a-foothold), detection difficulty (does the victim see anything?), and persistence (does access survive the patch?). You write it as prose; if a number is required, it falls out of the prose, never the other way round.

⚠ Pitfall · CVSS is not a severity rationale

CVSS scores the bug; a provider must score the architecture. CVSS gives log_fdw and AttachMe base scores in the same "high" neighbourhood — the provider-grade rationale does not put them within a continent of each other. A finding report that stops at "CVSS 9.8" has not done the job; the job is the paragraph.

Here is the chapter's keystone — the same five factors applied to two bugs of the same CWE and the same CVSS neighbourhood, producing opposite real severities.

Factorlog_fdw path traversal (#015)AttachMe (#086)
Blast radiusOne tenant — the attacker's own single-tenant DB host.Every OCI tenancy with a detached/shareable volume in scope — a core service, region-wide.
Access gainedLocal file read on one host.Read-write block-device access to victims' disks → code execution on next boot.
Exploitation costPost-auth: needs a paid DB and the extension enabled.Post-auth but near-free: a valid OCI account, one instance, a public OCID.
Detection difficultyHost-local activity; visible to the affected (attacker) tenant.The attach logs in the attacker's tenancy — the victim sees nothing.
PersistenceNone beyond the session.A modified boot volume re-executes on every subsequent victim boot.
VerdictMedium. A real bug, a footnote.Critical-plus. Provider patched in 24h; no customer action possible.

Same CWE. Same CVSS base. The architecture — what the broken component was shared with — is the entire difference. That is the chapter's thesis on paper.

For completeness, the maximum-severity specimen — the Entra Actor token, with every factor pegged. Blast radius: every Entra tenant on Earth. Access: Global Admin impersonation — full directory control. Exploitation cost: one free Entra tenant; pre-existing prerequisites only. Detection: near-zero — no sign-in logs, no Conditional Access, one obscure artifact. Persistence: 24-hour tokens, re-mintable at will until the global fix. There is no factor on which it scores anything but the worst available value — that is what "maximum severity" actually means, written as a rationale rather than asserted as a number.

One last case shows the defender winning by architecture. FabricScape (#066) is a container-to-node-to-cluster escape in Azure Service Fabric — the back-plane beneath Cosmos DB, Azure SQL DB, ACI and Azure Functions. The instructive part: the same exploit failed against ACI, Azure PostgreSQL and Functions, because Azure had disabled RuntimeAccess on multi-tenant Service Fabric clusters.[7]#066 Identical bug, neutralised by an architecture choice — step 4 of the blast-radius procedure made concrete. Design so a single bug is contained, not merely unlikely, because bugs are never unlikely enough.

Attacker's checklist · cross-tenant & provider-side
  • Name the boundaries. List all six for the target service; for each, identify the single component that enforces it.
  • Find the shared component. What does this service share — an agent, a cluster, a front-end, a namespace, a first-party principal, a certificate? That is your blast-radius lever.
  • Check whether enforcement equals attack surface. If the component you can talk to is the one enforcing isolation, fuzz its parsing — paths, headers, tokens, IDs.
  • Hunt the auto-installed. Enumerate provider agents on the host — cloud middleware. Each is root-privileged, undocumented, all-tenant.
  • Hunt the first-party principal. What provider identities live in the tenant by default, and what do they trust? An over-trusting one is a confused deputy you did not have to create.
  • Test the ownership check, not just the permission check. AttachMe-class bugs: the action requires a permission and an ownership validation; providers forget the second.
  • Reason the blast radius before you report. Run the five-step procedure, write the five-factor rationale, and never ship a bare CVSS.
Defender's mirror · containing the blast radius
  • Enforce isolation outside the tenant-reachable component. ChaosDB's lesson: firewall rules inside the customer container are removable by a root attacker (iptables -F). Isolation must be enforced where the tenant cannot reach it.
  • Do not send privileged credentials into the tenant boundary. Azurescape's bridge service-account token carried cluster-wide pods/exec. Use short-lived, audience-scoped tokens.
  • Validate ownership separately from permission — and use static analysis to find every API that checks one but not the other. This was Oracle's own stated remediation for AttachMe.
  • Containment beats elegance. FabricScape was neutralised on ACI and Functions by disabling RuntimeAccess — an architecture choice, not a patch. Design so a single bug is contained.
  • Close the cloud-middleware gap. Inventory every agent the provider installs; onboard them to auto-update; make their existence visible to customers.
  • Detection for the invisible. The Entra Actor token left exactly one artifact — a modifying action logged with a Global Admin UPN against a service application's display name. Provider-side detection means building signatures for contradictions, because the normal auth telemetry is empty.
  • Track un-CVE'd cloud bugs. Isolation breaks historically received no CVE. Community projects like cloudvulndb.org exist to close that gap.
◆ Key takeaways
  • Severity is set by what the broken component is shared with, not by the bug class or the bug's cleverness. Two identical bugs at different sharing scopes are two different catastrophes.
  • Every cross-tenant break defeats one primary isolation boundary — network, identity, hypervisor/kernel, namespace, account/subscription, or naming. Classify by the boundary whose failure delivered the cross-tenant impact, even when the chain touched several.
  • Identity is the most dangerous boundary: an identity break needs no further pivot — you simply are the victim.
  • A provider-side bug sits in a component the provider runs for all tenants at once; one exploitation is simultaneously an attack on everyone. Cloud middleware and first-party service principals are its two recurring shapes.
  • Blast radius is a quantity you compute — component → sharing scope → access → minus containment → a set of tenants. Severity is the five-factor rationale you write around it. CVSS scores the bug; you score the architecture.
  • Point patches make a bug harder; architectural fixes — removing the shared-blast-radius condition — make the whole class of bug contained.

References

  1. Wiz Research, "ChaosDB Explained: Azure's Cosmos DB Vulnerability Walkthrough". Archived: local copy · Original: wiz.io. Corpus #056.
  2. Wiz Research, "AttachMe: critical OCI vulnerability allows unauthorized cross-tenant volume access". Archived: local copy · Original: wiz.io. Corpus #086.
  3. Wiz Research, "OMIGOD: Critical Vulnerabilities in OMI (Azure)". Archived: local copy · Original: wiz.io. Corpus #057. Follow-up (CVE-2022-29149): local copy · wiz.io. Corpus #087.
  4. Dirk-jan Mollema, "Obtaining Global Admin in every Entra ID tenant with Actor tokens" (CVE-2025-55241). Archived: local copy of corpus stub · Original analysis: dirkjanm.io · Tracking: cloudvulndb.org · MSRC: CVE-2025-55241. Corpus #246.
  5. Binary Security, "Azure's Weakest Link? Full Cross-Tenant Compromise via API Connections". Archived: local copy · Original: binarysecurity.no. Corpus #197.
  6. Ian Duffy, "Azure Bug Bounty: Pwning Red Hat Enterprise Linux" — public admin access to Azure's Red Hat Update Infrastructure. Archived: local copy · Original: ianduffy.ie. Corpus #068.
  7. Palo Alto Networks Unit 42, "FabricScape: Escaping Azure Service Fabric" (CVE-2022-30137). Archived: local copy · Original: unit42.paloaltonetworks.com. Corpus #066.
  8. Palo Alto Networks Unit 42, "Azurescape: Cross-Account Container Takeover in Azure Container Instances". Archived: local copy · Original: unit42.paloaltonetworks.com. Corpus #054.
  9. Datadog Security Labs, "A confused deputy vulnerability in AWS AppSync". Archived: local copy · Original: securitylabs.datadoghq.com. Corpus #074.
  10. AWS, "Arbitrary local file read via path traversal in the RDS PostgreSQL log_fdw extension". Archived: local copy. Corpus #015.