Azure's Weakest Link – Five Full Cross-Tenant Compromises

Hacker News by 11 min read 509x views
Azure's Weakest Link – Five Full Cross-Tenant Compromises

Share Post

In my former blog posts, Azure’s Weakest Link? and Azure’s Weakest Link - Full Cross-Tenant Compromise, I gave an overview of the seriously insecure architecture rearward API Connections in Azure, a part of Azure Logic Apps, and an case of a complete cross-tenant colony using these inherent flaws. Now I am rear alongside several additional vulnerabilities in this system, all giving the identical primitives to the attacker. In total, the vulnerabilities current netted me a chill $200,000.

I additionally held a conversation detailing this at Blue Hat Asia 2026, and whenever the recordings are out, I volition add a nexus here.

TL;DR

API Connections authorize anyone to completely colony any another association worldwide, giving complete admission to the connected backend. This includes cross-tenant colony of Key Vaults and Azure SQL databases, as fine as any another externally connected service, specified as Jira or Salesforce. The lone item stopping exploitation is really the layers of input validation bolted onto the assorted systems, but as you volition see, it is really difficult to capture all the border cases here.

Architecture

If you haven’t peruse the archetypal parts of this series, I would propose checking those out first, Azure’s Weakest Link? - Part 1 and Azure’s Weakest Link - Full Cross-Tenant Compromise, but if you don’t care, I volition go through the most crucial parts of the architecture first.

From Microsoft’s documentation, we see a fairly intriguing diagram of how the API Connection architecture is built.

Microsoft's documented infrastructure

This basically spells out all that is needed to comprehend how the scheme operates, kudos to whoever made it!

It plant akin this

  1. A logic app, the sign on the base left, queries a shared Azure API Management instance
  2. The API Management case archetypal checks the swagger (OpenAPI) definition of the connector category and checks that it is a valid action
  3. A kind of key toggle happens anywhere the input token, or key, is exchanged for the configured token for the backend service
  4. The API Management case finishes the call alongside this new token to the backend instance.

From this, we can surmise that if we are capable to trick the API Management case to run on a distinct association than the ones we own, we would be capable to efficiently use the configured token for a victim’s association on their backend service.

This assistance can, in effect, be anything. As you can see here, the catalog of backend services is efficiently unbounded and includes a figure of Azure services.

However awesome the diagram is, there is one crucial omission, which we have already used to awesome consequence in part 2 of this series. By using the ARM REST API DynamicInvoke or /extensions/proxy/ endpoints, we can immediately query the APIM assistance through ARM. I have taken the liberty of updating the diagram to equivalent this.

Updated Connector Architecture

Considering this, and the fact that, as a asset manager, ARM basically has complete admission to everything, our exploitation pathway becomes clear. We must trick the calling assistance into querying a distinct association than ours, and thus get complete authority of the backend service.

Caveat: It is, of course, imaginable for the group who set up these connections to range their admission tokens or keys so that the API Connection has minimal privileges in the backend. I would guess, however, that this is rarely done. In the case of OAuth connections, it volition certainly continually be the token of the individual who initially made the connection. And for the API key case, well, who cares adequate to range specified things minimally? Microsoft doesn’t, at least.

Initial Exploitation

The archetypal cross-tenant exploitation I achieved in API Connections, and in fact in Azure as a whole, has already been described in part 2. As a recap, current is an example payload using the DynamicInvoke endpoint of my own association to peruse the Key Vault of a distinct tenant.

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" }, } 
HTTP/2 200 OK Cache-Control: no-cache Pragma: no-cache Content-Length: 1329 { "response": { "statusCode": "OK", "body": { "value": "dontreadme", "name": "victimsecret", "version": "7914d45aa60342809fb8cc12dc68b10e", "contentType": null, "isEnabled": true, "createdTime": "2025-04-04T05:38:26Z", "lastUpdatedTime": "2025-04-04T05:38:26Z", "validityStartTime": null, "validityEndTime": null }, "headers": { <Headers> } } } 

As you can see, it’s a beautiful apparent way traversal vulnerability. This implies what we already assumed: When the petition fires from ARM to the APIM instance, it has entitlements to all connections, despite of what it operates on.

Further exploitation

A brace of weeks following I reported this vulnerability, it got marked as fixed, and whenever I tried it again, the following error communication greeted me:

Vulnerability fixed by restricting the path

Now, I considered whether or not this was a adequate fix. It seemed to restrict the paths allowed, fairly than the underlying token issue, so I guessed there would be ways about it.

An engaging small fact concerning the ARM API is that, in several cases, it power not be as much of a dreary box as you assume. The applicable code can sometimes be established inside the asset types themselves. I do not cognize whether this code really executes on these machines or is merely included as an artifact, but it is a aureate quarry of engaging information. So, I created a Standard Logic App, which provides a completely dedicated host, SSHed into it, and extracted the code. Lo and behold, appearance what I found:

The validation code paths contain a inspect for `../`

Can you see the fix they implemented?

What was equal additional engaging current really was item I found, and was not really equal looking for. When searching for the genuine DynamicInvoke code, what greeted me was not really fair this one community function, but four:

I established another function definitions correct next to Dynamic Invoke

These functions are entirely undocumented, and I could discover no citation to them anywhere. However, they were reachable and located alongside the identical path. So, for instance, to hit DynamicList here, merely alter dynamicInvoke in your URI to DynamicList.

Knowing that the slap-dash blacklisting fix that they implemented lone applied to DynamicInvoke, I assumed that these would all be susceptible in the identical way if they had the identical functionality. Without boring you too much alongside the required arguments and come back values here, the exploitation method is really fairly the same, although the payload is a bit additional involved. For DynamicList, you need to define all way indicator on its own and do a bit of activity to get a result, but the identical logic applies. To achieve, for instance, a cross-tenant compose on a victim’s SQL server through their connection, a payload akin this works:

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)" } } }, "ItemsPath": "ResultSets/Table1", "ItemValuePath": "secrets", "ItemTitlePath": "value" } } 

You can fairly effortlessly see that the archetypal parameters current execute the way traversal and afterward execute an arbitrary SQL query on the database.

I reported lone the DynamicList case first, in the anticipation that they would execute a akin fix to DynamicInvoke and I would get to study on the another two as well. Sadly, their fix engaged merely blocking all these endpoints. No another mitigations appear to be in place, however, so if these endpoints always rotate into accessible again, I would assume they would be vulnerable.

Further exploitation

Messing about alongside paths in these energetic calls seemed to have reached its conclusion: I couldn’t get former the way restrictions on DynamicInvoke, and all the another endpoints were blocked. What I needed was a distinct way in. As I was trying to sleep one day, it occurred to me that I had overlooked using the Logic Apps themselves. I realized that there was, in fact, a important difference between the Consumption and Standard Logic Apps connection-creation flows. The most glaring difference is that Consumption Logic Apps, anywhere your workflow is co-located alongside a bunch of others, create a Version 1 connection, during the dedicated-host Standard Logic Apps create a Version 2 connection. The following diagrams create a additional subtle difference between the flows clear:

The V2 Connection Creation Flow.

The V1 Connection Creation Flow.

Do you see the difference? Obviously, whenever it’s spelled out akin that, it’s beautiful apparent what the item is. Using a Standard Logic App, following we have created the asset and added any authentication to the backend server we need, we authorize that particular logic app to admission it using its managed identity. However, whenever we have a Consumption Logic App, we don’t have any specified character to authorize since we don’t authority it. This perchance makes several sense: we can’t authorize it, so we don’t.

I realized that this must connote that all Consumption Logic App has implied admission to every Version 1 API Connection; the lone item stopping us from exploiting this must be several kind of input validation.

The traditional perspective of a Logic App workflow is fairly restrictive in its input, but there are the two the API endpoints, and the Code perspective in the GUI that authorize us to perform about a bit additional alongside the inputs.

The code perspective allows for additional fine-grained control.

This allows us to see the inputs in additional detail, edit them, and equal add more. The apparent trick of changing the referenced connector to a victim’s association fails, as the runtime performs extended validation that you own the connection. We have the codebase, though, so we can try to discover all the valid parameters. It did not obtain much searching for me to detect the host.api.RuntimeUrl parameter.

I can’t fairly get my caput about why this indicator is included, nor why it’s motionless accepted to this day, but it plant in the way you would expect. After performing a sequence of validations on the association referenced in host.connection, you can define that it should use item entirely distinct alternatively of the runtimeUrl baked into that connection. As lengthy as the presenter ends alongside azure-apihub.net, it volition additionally contain the required Authorization header.

Instead of using the connection's `runtimeUrl`, use a distinct one.

What happens whenever this workflow is run then?

The victim's concealed is returned.

The keen-eyed audience power notice that the runtimeUrl used in the utilize is not fair the connectionId. This is since several additional elements get added to the URI, but it is of no consequence to us; the utilize plant fine.

Manna from the heavens

I, of course, reported this to Microsoft again, and they spent several period operating up a fix. When it eventually came through, the utilize returned an error message:

The presenter API runtime URL is not valid for the API association '/subscriptions/8e3ce52f-d45b-4347-8705-65892507465e/resourceGroups/token-storer/providers/Microsoft.Web/connections/keyvault-1'. The runtime URL way must equivalent the expected API endpoint 'https://logic-apis-norwayeast.azure-apihub.net/apim/keyvault/cbfb0f4313144117a7440368902bb871?' for the referenced connection. Either omit the 'host.api' input property, or accurate the runtime URL to equivalent the API connected alongside the connection. 

Except for the excessive dimension of this message, it seems akin fairly a suitable fix. The runtime URL must exactly equivalent what is expected; otherwise, no dice. Apart from fair removing the indicator altogether, it’s fairly sane. Unless, of course, it checks that value at the incorrect time:

Path traversal in RuntimeUrl.

This plant again, alongside the identical result. What’s going on current is that the validation happens before the way is normalized, so the runtime thinks the URI is pointing to my referenced connection, but the APIM case sees it differently.

And fair akin that, I got another crucial vulnerability.

The fun does not halt there. When Microsoft managed to scrape together a fix for this way traversal, a amazing item happened. The first RuntimeUrl way was no longer validated, and as a result, you could execute that assault again. I would contain several pictures, but they would be the identical pictures as above, so you can fair ideate them.

Reporting and rewards

Altogether, API Connections have netted me five crucial elevation-of-privilege vulnerabilities in Azure, which is perchance additional than expected. All vulnerabilities reported current were fixed inside a sensible period following I submitted the reports, and during there were several disagreements alongside MSRC alongside the way, overall, I am fairly blessed alongside the results.

Other Article Hacker News
↑
Close Right Ads
Close Left Ads