Summary
In this write-up, I’ll discuss OData and the attack scenarios I tested on Microsoft Forms. As you know, Office 365 includes a variety of services, one of which is Microsoft Forms. This online survey creator allows users to create forms and share them with others. Microsoft Forms interacts with data through OData. But what exactly is OData?
OData Basics
The OData Protocol is an application-level protocol for interacting with data via RESTful interfaces. It supports the description of data models, editing and querying of data according to those models. [docs.microsoft.com]
OData is based on SQL principles. It can process requests from any type of client (such as .NET or Java) without worrying about the client type or the data source of the site.
For simplicity, let me explain some fundamental concepts of this protocol. OData metadata serves as the data model for the system (think of it as the information_schema in relational databases). Each metadata contains entities (similar to tables in relational databases) and properties (akin to columns), as well as the relationships between different entity types. Every entity type has an entity key, which is comparable to keys in relational databases:
customerApi/Customers(2)
This request returns customer information for ID=2. Similar to SQL, OData supports various query options for data retrieval. One such option is $select, which allows us to request a limited set of properties for each entity. For instance, the following query fetches the email from the Customers entity for the customer with ID=2:
customerApi/Customers(2)?$select=email
In SQL, this query would look like:
SELECT email FROM Customers WHERE ID=2;
The concepts I’ve discussed so far are enough to understand the continuation of this discussion. Besides $select, several other query options exist. You can specify the output data format using $format, allowing you to choose between JSON and XML. For more details and familiarity with other query options, I recommend this link, which serves as the primary reference for OData.
Attack Scenarios
“One thing I always keep in mind is that I first try to get an overview of the system before testing each of its components.”
In this case, I tried to access the OData metadata since I knew the target employed OData, and understanding the structure of the metadata was essential. By sending a request to the following URL, I accessed the target’s metadata:
http://forms.office.com/formapi/api/$metadata
The above document, which is an XML, was not interesting to me. So I used the following website to see the connections between different entity types:
This website serves as a generic OData API/service visualizer and explorer. It offers a feature where you can input the OData metadata link, allowing it to display the connections between various entity types.
After gaining an overview of the metadata, I searched for entities that might harbor sensitive information. I found an entity type called forms, which contained data from user-generated forms, including users’ email addresses:
This discovery raised the question: how could I access other users’ email addresses? IDOR (Insecure Direct Object Reference) and CORS (Cross-Origin Resource Sharing) exploitation didn’t seem feasible here. However, after testing different attack scenarios, I identified a method to access another user’s email. The challenge was that my attack required multiple user interactions, which minimized the impact of my scenario. After submitting my findings to MSRC, I was pleased to see my name included in the Microsoft Hall of Fame.
Unauthorized Access to OData Entities
After not receiving a bounty, I reconsidered my approach to the target. My previous attack required user interaction, so I aimed to find a method to access email addresses without such interaction.
In Microsoft Forms, users can share forms with others. Here’s how the sharing process works if User A wants to share a form with User B:
- User A selects a form to share, prompting the server to generate a shareable link.
- User A sends the generated link to User B.
- When User B submits the form, the data is sent to the server, and User A can view the submitted information in their account.
In the third step, the following request is sent:
The above request has the following structure:
formapi/api/<ownerTenantID>/users/<ownerID>/forms(<formID>)/responses
The structure of the request includes parameters like ownerTenantID, ownerID, and formID, which are associated with the user who shared the form with us. Once we submit the user’s form, we have access to all these parameters; therefore, we don’t need to find any of them.
This scenario led me to consider: since I had all the necessary parameters from the victim, why not utilize the $select query option to retrieve the victim’s email from the server?
I sent the following request, but it resulted in a 404 error. The server denied access to the createdBy property, which contains the user’s email on the forms entity. I searched for an alternative way to extract the value of the createdBy property and thought:
“Is there another entity that possesses a createdBy property and shares the same entity key (formID) as the forms entity?”
You might wonder why the entity key should match that of the forms entity.
Assuming the entity we seek is called X, it must have the createdBy property. The goal is to access the value of this property, which is the user’s email. However, if X has an entity key called accountID, which is a random string, accessing the email would require submitting the following request:
formapi/api/<ownerTenantID>/users/<ownerID>/X(<accountID>)$select=createdBy
The issue arises with accountID, as we typically wouldn’t know the victim’s accountID. However, we previously established that we had access to the formID through the shared form.
After reviewing the relationships between entity types, I discovered an entity named runtimeForms that contained the createdBy property and shared the same entity key as forms! This aligned perfectly with the conditions I needed.
By using runtimeForms instead of forms, and then applying the $select query option to access the email, I successfully executed the following request and finally retrieved the victim’s email:
With this attack scenario, I could access users’ emails without requiring user interaction. Ultimately, I received a $2K bounty from MSRC.
I hope you found this write-up engaging. Always look for edge cases in your targets to discover more intriguing vulnerabilities.