Cloud 101CircleEventsBlog
Missed CSA's Cyber Monday sale? You can still get 50% off the CCSK + CCZT Exam & Training Bundle and Token Bundle with raincheck code 'rcdoubledip24'

Microsoft Power Pages: Data Exposure Reviewed

Published 12/09/2024

Microsoft Power Pages: Data Exposure Reviewed

Originally published by AppOmni.

Written by Aaron Costello, Chief of SaaS Security Research, AppOmni.


This blog post explores a significant data exposure issue within Microsoft Power Pages, a low-code SaaS platform, due to misconfigured access controls. It highlights how sensitive PII can be inadvertently exposed to unauthorized users when organizations grant excessive permissions to the Anonymous role . The key points include understanding Power Pages’ Role-Based Access Control (RBAC) model, mismanagement of authentication and column-level security, and the risks posed by over-permissioned access to databases. The post emphasizes the importance of continuous monitoring to identify misconfigurations and protect against data breaches.

In September 2024, I uncovered significant amounts of data being exposed to the public internet as a result of misconfigured access controls in Microsoft Power Page websites. Power Pages is a low-code SaaS platform, built on top of the Power Platform, which allows individuals to build externally facing websites on Microsoft’s infrastructure. The main benefits of Power Pages over traditional custom web development include out-of-the-box (OOB) role based access control (RBAC), the automatic ability to use Microsoft’s Dataverse as a database, and a drag-and-drop interface using pre-built components which greatly reduces the need for custom code.

However, the ability for Microsoft customers to easily deploy these data-driven web applications can come at a great cost to security if mismanaged from a security perspective. During my research, I’ve uncovered several million records of sensitive data being exposed to the public internet from authorized testing alone. The primary nature of this data are internal organization files and sensitive PII belonging to both internal organization users and other users registered on the website. In the majority of these cases, the PII uncovered included full names, email addresses, phone numbers, and home addresses.

In one case, a large shared business service provider for the NHS was leaking the information of over 1.1 million NHS employees, with large portions of the data including email addresses, telephone numbers, and even home addresses of the employees. This particular finding was responsibly disclosed and has since been resolved.

These data exposures are occurring due to a misunderstanding of access controls within Power Pages, and insecure custom code implementations. By granting unauthenticated users excessive permissions, anyone may have the ability to extract records from the database using readily-available Power Page APIs.

For AppOmni customers monitoring Microsoft365 products, I had already developed and made available an AppOmni Insight that assists with detecting these kinds of exposures and provides subsequent remediation guidance in the event an exposure is found.

To better understand how these exposures can occur, it is best to first have a firm grasp on basic Power Page architecture and the platform’s RBAC model.


Basic architecture of Power Pages

This section does not provide a complete view of the Power Pages architecture, only the necessary components for which comprehension of this article requires.

A user’s interactions with a Power Pages site.'

Figure 2: A user’s interactions with a Power Pages site.

The above diagram is a simplistic representation of a user’s interactions with a Power Pages site. Generally speaking, a regular user will interact with a page, which leverages one of three different API specifications to perform CRUD access on data stored within Dataverse, a cloud-based relational database. These API specifications behave differently, and each one must be explicitly enabled to use them. For the purposes of exploitation, I used the Web API, which is the Power Platform’s newest public API implementation. This was for a number of reasons:

  1. In 2021, researchers at UpGuard discovered instances of data exposure resulting from public OData feeds, however these feeds are now considered a deprecated legacy feature by Microsoft. This resulted in API implementations of this kind being few and far between, with organizations instead favoring the newer Web API.
  2. Embedded Lists require pre-existing knowledge of uniquely generated identifiers if not embedded on an accessible page, proving enumeration of their endpoints difficult.
  3. Neither OData feeds nor Embedded Lists allow for operations other than read, whereas the Web API allows for full CRUD operations on data if the user has the required permission. Whilst those other operations are not the focal point of this article, they can lead to some interesting DML based vulnerabilities.


Intro to Power Pages’ Role Based Access Control (RBAC)

In this section, I will explain the specifics of Power Pages’ RBAC functionality and their impact.


Roles

Each Power Pages site has three out-of-the-box (OOB) roles that may not be deleted or deactivated. Every individual who accesses the site has a role associated with them, regardless of if they are logged in or not. Furthermore, each role will be assigned varying levels of access to data; external roles generally being less powerful than roles representing internal users. For the scope of this article, I will be focusing on the ‘Anonymous Users’ and ‘Authenticated Users’ roles specifically. Anonymous Users represents all individuals who have not yet authenticated to the site, and as such needs no further explanation as to its importance in this article.

The reason that ‘Authenticated Users’, a role which represents anyone logged into the site, is important, is because many organizations have public registration enabled. This allows any individual to make an account on the site. Therefore, once an individual creates an account, the user will receive an ‘Authenticated Users’ role and all of the superior permissions that may come with it. In these cases, it can effectively be treated as an ‘external role’ as opposed to an internal one. This is of key significance for this piece, as organizations are far more likely to grant excessive permissions to a role that they believe is internal in nature.


Access levels

As is typically the case with powerful SaaS platforms, Power Pages has a layered approach to access controls that allows for finely tuned permissions to be provisioned to particular roles and users. The four levels that these access controls exist for a ‘read’ operation are:

Power Pages' layered approach to access controls.

Figure 3: Power Pages’ layered approach to access controls.

Within the following subsections, I will delve further into the key elements of each level, explaining further their individual intricacies and capabilities with respect to implementing access controls.


Site level

At the foundation of the diagram are site-level settings, or as in this case, site-level access controls. And each Power Pages site that an organization has deployed may have different configurations. These are managed within a site’s ‘Power Pages Management’ section and may be created, modified, and deleted. With respect to authentication and authorization, below are a list of key controls that are particularly relevant to this article:

Setting NameCategoryDescriptionMust Also Be EnabledDefault Value
Authentication/Registration
/Enabled

Authentication
Enables or disables all forms of user registration.
Registration must be enabled for the other settings in this table in order for this to take effect.
true
*Authentication/Registration
/OpenRegistrationEnabled
AuthenticationEnables or disables the sign-up registration form.
The sign-up form allows any unauthenticated visitor to create a user account.

Authentication/Registration
/Enabled
true
Authentication/Registration
/ExternalLoginEnabled
AuthenticationEnables or disables sign-in for external users with an account.true
*Authentication/Registration
/LocalLoginEnabled
AuthenticationAllows for login using username / email and password.Authentication/Registration
/LocalLoginDeprecated
true
*Authentication/Registration
/LocalLoginDeprecated
AuthenticationDisables login using username / email and password, even if ‘LocalLoginEnabled’ is enabled.false
Webapi/AuthorizationAllows a table to be accessible via the Web API.
Requires a corresponding ‘Webapi/<object>/fields’ setting to exist for the table.
Webapi/<object>/fieldsAuthorizationComma-separated list of columns that belong to a Web API enabled table which are to be accessible.
Each table will have its own setting.
Webapi/<object>/enabled
* These settings do not account for registration or authentication through other means such as an invitation link or IdP, those are managed through other settings.


Settings in the above table that are categorized as dealing with ‘Authentication’ are crucial for the sole reason that they will define the scope of what a security practitioner or administrator will be auditing. From what was briefly discussed in the Roles section, we know that if an unauthenticated user may self-register, then the ‘Authenticated User’ role should be treated as an external one alongside the ‘Anonymous Users’ role when it comes to an access control audit.

In addition, there are two Webapi-prefixed settings that contain placeholders. These settings are always created as a pair, and must be created for every table and its respective columns that an organization wishes to expose via the public Web API. Otherwise they will not be available to be queried or used by the site through that mechanism. For that reason, platform administrators must take note of any tables and columns that are marked as Web API accessible through these settings, as they are the ones explicitly at risk of being leaked to unintended audiences.


Table and record level access

Once a table and its columns have been exposed to the Web API, access to the table must be granted to the desired roles. These permissions, along with record-level permissions, are granted within the ‘Table Permissions’ section of an individual site’s Power Pages management portal. Below are a subset of the key columns that an administrator can define the following key columns when assigning permissions:

  • Table: The name of the table to which access will be granted
  • Role(s): The roles to which this access is granted to
  • Permission To: A list of CRUD operations that an authorized user may perform against the data. These include ‘read’, ‘update’, ‘append’, ‘append to’, ‘create’, and ‘delete’.
  • Access Type: This is the ‘record level access’ implementation for Power Pages. It provides a list of options from which only one may be selected, and this selection determines which records a user may be able to access from the table. It is akin to a very basic Sharing Rules implementation within Salesforce.

Taking the above template into account, nearly all data exposures I’ve come across have been the result of the following inclusions within a table’s access control definition:

  • Role(s): ‘Anonymous Users’, and ‘Authenticated Users’ but only if public registration is enabled
  • Access Type: ‘Global Access’, which grants access to all rows within a table


Column level access

Once a user satisfies table-level access controls, any existing column access controls are then applied. Unlike table-level access controls, Microsoft’s column-level access control implementation relies on what is called ‘Masking’. Masks are customer-configurable regexes that allow customers to obfuscate certain columns that match a regex pattern, from users without the correct roles, that are deemed to be sensitive in nature. This is done by replacing content within the column which matches the regex, with a value of the customer’s choice such as ‘*’. A real example of this, provided by Microsoft is the following regex, \d(?=\d{2}-\d{2}-\d{4}\|\d-\d{2}-\d{4}\|-\d{2}-\d{4}\|\d-\d{4}\|-\d{4}), which can be used to hide social security numbers from users.

Unfortunately, the setup process is quite a number of steps for something that is typically solved within a single page on other platforms. Let’s create a scenario in which an organization wishes to block an unauthenticated user from seeing a column that stores home addresses. To do so, they would need to do the following:

  1. Within Power Apps, create a catch-all regex so that the column contents will be obfuscated regardless of the value.
  2. Within Power Pages, enable column-security for the column.
  3. Within the PowerApps Management portal, create a column security profile for the table associated with the column.
  4. Associate the role(s) of any users you wish to be able to view the plaintext value of the column to the security profile
  5. Within the same security profile, create a ‘read’ column permission for that column

Throughout the entirety of my testing, not a single implementation of column-level security was present to prevent access to sensitive columns. Whether this is due to the initially tedious setup duration, or the fact that creating your own regexes is a pre-release feature ‘not intended for production’, this security feature is widely slept upon by organizations.


Causes and contributors of exposure

  1. Exposing excessive columns to the Web API: This in itself is not an issue per se, however it does greatly increase the blast radius of an exposure in the event that one occurs through over-permissioning. Organizations that rely heavily on the Web API have a tendency to allow-list all columns of a single table, by setting the value of ‘Webapi/<object>/fields’ to ‘*’. The result of this configuration is all columns being retrievable by the Web API.
  2. Allowing open registration and external authentication: By default when a site is deployed, self-registration and login is enabled for the site. Even though these pages may not be visible on the platform, users may still be able to register and authenticate through the associated APIs. Generally speaking, users with the ‘Authenticated Users’ role have more permissions than those that have the ‘Anonymous Users’ role.
  3. Granting global access tables for external users: By granting the ‘Anonymous Users’ role ‘Global Access’ for read operations to a table, the public gains unrestricted read access to all rows of data regardless of record ownership. If an organization has enabled both external registration and external login, it is important to include the ‘Authenticated Users’ role in our definition of ‘External Users’.
  4. Not enabling column security for sensitive columns: Without leveraging column security profiles, all columns that are web API enabled will be shown to external users if permissions at the table level are misconfigured. Column security profiles allow organizations to add an extra layer of security, by strictly limiting the ability to access sensitive columns for privileged internal users.

Not implementing masks for sensitive columns: Throughout my authorized testing, I did not come across the use of obfuscation for sensitive columns. If an organization does not wish to leverage column security profiles, it may be wise to apply masks to PII related columns exclusively for external users, without hindering site functionality.


Technical proof of concept

A prerequisite for testing is a proxy tool such as Burp Suite, which can sniff HTTP(s) traffic and modify / replay requests.

In this section, I’ll explain how these access control misconfigurations can be exploited, at a technical level. All queries seen in this section were tested against a personal public site of Power Pages, which has been intentionally misconfigured for the purpose of this demonstration. The configuration of this vulnerable instance is as follows:

  • Site Settings:
    • Webapi/account
    • Webapi/account/fields: *
    • Webapi/contact
    • Webapi/contact/fields: fullname,telephone1
  • Table Permissions:
    • Table: account
      • Access Type: Global Access
      • Role: Anonymous Users
    • Table: contact
      • Access Type: Global Access
      • Role: Anonymous Users

To make it a more ‘realistic’ exercise, I’ll also include requests to tables and columns which are deliberately not exposed. This gives the benefit of differentiating between the various API errors, and how to pivot around them. Additionally, in a legitimate red team exercise, it is recommended to export the entire Power Platform schema, both tables and their columns, into a wordlist-style format to be used in a brute-force fashion.


Identifying exposed tables and exploiting wildcard columns

  1. The first step is navigating to the Power Pages site while Burp Suite is sniffing traffic to capture at least one request. This request does not need cookies to be included, so even a request to ‘/’ as shown below will suffice.

Figure 4: Capture a request on the Power Pages site.

2. Once a valid request has been identified, it can be sent to Burp’s Repeater tab to be modified. Once done, the only modification that needs to be made is changing the URL path to ‘/_api/<object_name>’. For the first example, we will attempt to access a column which has not yet been exposed, ‘sharepointdocuments’.

The error is associated with a table not exposed to the API

Figure 5: The error is associated with a table not exposed to the API.

In the above, there is a distinctive error message that is uniquely associated with an object which has not been exposed to the Web API. Notably, if a valid table name is provided in the query, it is the only case in which a 404 HTTP status code is returned.

3. Now, we’ll replace ‘sharepointdocuments’ with an object that we know is exposed, ‘accounts’, and re-send the request.

A successful HTTP 200 API response.

Figure 6: A successful HTTP 200 API response.

A successful response like the above will return a list of maximum 5000 records at a time, with all columns returned. From an external perspective, having a maximum record count of 5000 is less than ideal when trying to assess the total impact of the exposure. Luckily, there are additional GET API parameters that can be played with to attain a more desirable result, detailed below:

  • $orderby: Used to order results based on one or more columns provided; This parameter expects at least one column name, with either the desc (descending) or asc (ascending) keyword. Multiple columns must be comma delimited.
  • $top: Maximum number of records to return in the response; In the event that there are more records available than what can be returned in a single response, the response will contain a “@odata.nextLink”. This will have a URL path and parameters that can be used to query the next page of data.
  • $select: Takes a comma-delimited list of specific columns of interest; This can greatly improve the readability of the response by removing typically non-sensitive columns such as ‘donotemail’ etc.
  • $filter: Takes expressions that result in only records matching the expressions being returned; When a large number of records are returned in a response, this can be useful to narrow down those exposing a specifically sensitive column.

4. Knowing what a successful response looks like, let’s replace ‘account’ with the ‘contacts’ object, then send the request.

HTTP 403 status code for unsupported operation

Figure 7: HTTP 403 status code for unsupported operation.

In the above, querying all columns returned an error since not all columns for this exposed object have been made available to the Web API. It’s important to take note of the error code in the response also, as “90040101” is only generated when an unsupported column, or in this case an attribute, is not supported or enabled.

5. To resolve this issue, we must manually determine which columns have been exposed publicly and this can be done using the $select parameter. This is an exercise in trial-and-error, so it is recommended that the schema is exported for easy automated testing.

Unsuccessful attempt at enumerating columns

Figure 8: Unsuccessful attempt at enumerating columns.

Based on the error message, it can be determined that at least one column is not exposed, in this case it’s the ‘fullname’ column. If this column was removed from the $select parameter, it could generate another error of the same format in the event that the ‘telephone1’ parameter was also not exposed.

6. Removing the ‘fullname’ column, we will resend the request:

A HTTP 200 successful response

Figure 9: A HTTP 200 successful response.

By removing the inaccessible field, I extracted the exposed data by ‘guessing’ the other columns. One last behavior worth mentioning not covered above is if an object is queried that is Web API accessible but not to the ‘Anonymous User’ role, a HTTP 403 response with the message, “You don’t have permission to read the X table.” will be returned. This is a good case for self-registering if possible and re-sending the request authenticated, as the ‘Authenticated Users’ role may have the required access.


Awareness of issues and mitigation techniques

Acknowledge the warning signs

Microsoft has helpfully included a number of warnings in the backend of the Power Pages and Power Platform applications when a potentially dangerous configuration is detected, such as public data. These include:

  • A banner on all Power Platform admin console pages, which warns that if a site is public, any changes made will be visible immediately.
  • An informational message within Power Pages’ table permissions configuration page, informing administrators of the risks of using the ‘Anonymous Role’ in table permissions

Figure 10: An informational message appears within Power Pages’ table permissions configuration page.

  • On the same page, a warning icon is displayed beside any permission granting ‘Global Access’ to the ‘Anonymous Users’ role

Figure 11: A warning icon appears near any permission granting ‘Global Access’ to anonymous users.

If any of the above messages or icons are displaying on configuration pages related to permissions, it is advisable that organizations review their access controls to ensure that any public data that may be exposed is not sensitive in nature.


How to audit access controls

The most effective way to resolve this issue in its entirety is to remove excessive levels of access to external users. Personally, I recommend assessing access levels from the ground up. This starts with the analysis of site settings, then table / record permissions, and finishing with column permissions. The following are configuration items that are essential to cover:

  • Site Settings
    • Webapi/<object>/enabled
    • Webapi/<object>/fields
    • Authentication/Registration/Enabled
    • Authentication/Registration/OpenRegistrationEnabled
    • Authentication/Registration/ExternalLoginEnabled
    • Authentication/Registration/LocalLoginEnabled
    • Authentication/Registration/LocalLoginDeprecated
  • Table / Record Permissions
    • Any table that has the ‘Access Type’ set to ‘Global Access’ and is associated with external roles
  • Column Permissions
    • Any columns belonging to tables that are accessible to external users, which do not have column security enabled and an appropriate mask
  • Column Security Profiles
    • Identify column security profiles which include external roles

In certain cases, organizations that rely heavily on the Web API for custom functionality may not always be able to resolve these problems through the above, without breaking site functionality. For example, one common implementation that I’ve noticed is an organization leveraging the Web API on custom authentication forms to validate the existence of a user submitted employee number, or similar, exists. The common misconfiguration here was that there were often excessive privileges granted at both the record level and column level, resulting in complete exposure of the data set. In that case, leveraging a custom API endpoint for validation of user-supplied information would be recommended.


Continuously monitor SaaS security

Microsoft’s Power Pages platform is an excellent low-code solution for the development of Dataverse-integrated applications such as web portals. However, the customizable nature of the product can result in organizations with poor security hygiene accidentally exposing their most sensitive data to the public internet. Microsoft has done an excellent job thoroughly documenting the dangers of this API both in-product and throughout its documentation. Additionally, Microsoft’s security scan feature is an excellent tool to quickly diagnose potential access control misconfigurations. Yet, it is a point-in-time tool.

In today’s rapidly evolving threat landscape, maintaining SaaS security requires a proactive approach. Implementing continuous monitoring is not just a best practice—it’s essential for identifying vulnerabilities, detecting suspicious activity, and ensuring compliance with security standards. Leveraging a continuous monitoring tool enables businesses to maintain a secure state by providing real-time insights and automated alerts, allowing security teams to respond to threats swiftly and effectively. By staying vigilant and utilizing advanced monitoring solutions, organizations can confidently safeguard their critical SaaS environments against emerging risks and ensure long-term data protection.

Share this content on your favorite social network today!