> blog.faav.net

Logo

Developer @ capes.me (& NameMC Extras), Web developer, Bug hunter. Follow my 𝕏!

Microsoft Events Leak, Part I: Leaking Event Registration and Waitlist Databases (via OData Injection)

After this, please read Part II.

Date: 10/08/2025

Hey! I’m Faav, a 15 y/o amateur bug bounty hunter. This is the story of how I discovered a vulnerability to enumerate the entire Microsoft Events Registration and Waitlist databases, leaking full names, phone numbers, emails, and addresses for some.

One day I was scrolling through Twitter/X and I saw a post from GitHub with a link to a GitHub event with the domain events.microsoft.com. My interest was piqued. (The events.microsoft.com subdomain now redirects to https://www.microsoft.com/en-us/events/search-catalog)

image

I dug through the API endpoints and didn’t really find anything useful since it only returned standard event data. While exploring, I noticed that clicking the “Registration and details” button sent you to another subdomain msevents.microsoft.com.

image

When I looked through the requests a lot of endpoints piqued my interest, the first endpoint I checked was https://9d3afc07f6d343b0ad93f59bbf0029eb.svc.dynamics.com/EvtMgmt/api/v2.0/events/READABLE_EVENT_ID?emApplicationtoken=TOKEN_HERE. There were more endpoints that let you get registration count, sessions, and speakers but nothing returned anything interesting. I tested for injection vulnerabilities in the eventid, but found none.

OData Injection

I then noticed https://msevents.microsoft.com/api/GetEvents?eventid=EVENT_ID, I immediately tried injecting into the eventid parameter and it worked! There is an unterminated string literal at position 3 in '(')'.

The error showed it was using Microsoft Dataverse OData, a protocol to retrieve data from databases used by Microsoft’s Power Platform. (I know this from reading documentation)

So the injection point was like /api/data/v9.2/msevtmgt_events(INJECTION_HERE) (I learned this from the docs) so I could do ?eventid=/../msevtmgt_events# and more. (I found a lot of endpoints I could use like /accounts, /contacts, msevtmgt_speakers, etc) and I got a Status 200! Good, right? Wrong!

image

(All nulls)

Turns out the endpoint only returned certain fields from the response like msevtmgt_eventid and if I tried any of those other endpoints it would send an array not an object and if I used (UUID) to query a single object, it would return different fields anyway. I tried to see if I could enumerate field values with $filter or other parameters but it always returned Status 200 with the same response.

I tried path traversal to get to the $metadata endpoint but nothing worked. Exception of type 'System.Exception' was thrown.

Damn.

Looking for more

So I wanted to look for more endpoints under https://msevents.microsoft.com/api/, and I found /api/GetEventCustomRegistrationFields?readableeventid=READABLE_EVENT_ID.

OData Injection also worked in the readableeventid parameter but this time, the injection point was within the $filter parameter. I tried breaking out with %26$filter=test (%26 is url encoded &) but I got: Query option '$filter' was specified more than once, but it must be specified at most once.

When I put a ' it leaked the $filter query: There is an unterminated string literal at position 53 in 'msevtmgt_event/msevtmgt_readableeventid eq 'test123'''.

I then tried ' or '' eq ' but I got: The left side of the 'NotEqual' operator must be a property of the entity.

So after some fiddling I did ' or msevtmgt_event/msevtmgt_readableeventid ne ' and it worked:

image

This leaked all Microsoft events but the data wasn’t sensitive or particularly useful, I tried $expand to get more data, but got nothing. So onto the next endpoint!

Jackpot

I came across an interesting POST endpoint /api/CheckEventRegistration, if you sent the email and eventId fields it would return whether the email was already registered for the specified event. This immediately caught my attention.

I put a ' in the fields and bingo! There is an unterminated string literal at position 89 in '_msevtmgt_eventid_value eq '78009109-cff9-48fc-a6a8-e1a8dd5eeccd' and me_email eq 'test'''. (78009109-cff9-48fc-a6a8-e1a8dd5eeccd was a random UUID I generated that didn’t correspond to any event)

I immediately tried ' or me_email eq ' but it didn’t work? I tried both fields and neither worked… Could not find a property named 'me_email' on type 'Microsoft.Dynamics.CRM.msevtmgt_waitlistitem'.

This puzzled me for a while but while randomly testing I put a '# (# ignored the rest of the URL) at the end of eventId and it gave me a different error! Syntax error at position 80 in 'msevtmgt_contact/emailaddress1 eq 'test'' and _msevtmgt_event_value eq '78009109-cff9-48fc-a6a8-e1a8dd5eeccd''.

I immediately tried to do:

{
  "email": "' or msevtmgt_contact/emailaddress1 eq 'test@gmail.com",
  "eventId": "78009109-cff9-48fc-a6a8-e1a8dd5eeccd'#"
}

It gave me {"exists":false} and this is when I remembered about the OData functions I could use, startswith, endswith, and contains. (and more)

{
  "email": "' or startswith(msevtmgt_contact/emailaddress1, 'test')#",
  "eventId": "78009109-cff9-48fc-a6a8-e1a8dd5eeccd'#"
}

This returned {"exists":true}, success!

I quickly used Microsoft Copilot to write a script using startswith with Promise.all’s since there were no ratelimits, this could leak the emailaddress1 values for everyone in the database (This turned out to be the Waitlist database) I noticed more fields I could use like fullname, telephone1, address1_line1, company, employeeid, and more. (A LOT more) Combined with the and statement I could steal and correlate all the data in the database. I noticed a lot of @microsoft.com, .gov, and other corporate email addresses in this database.

image

At this point, I chose to stop my testing and report the vulnerability to MSRC.

After a few hours, I decided to come back and see what else I could access because that me_email parameter kept me thinking. I randomly tried reversing the fields injection points, like this:

{
  "email": "'#",
  "eventId": "78009109-cff9-48fc-a6a8-e1a8dd5eeccd' or me_email eq 'test'#"
}

Surprisingly, this worked and returned {"exists":false}, If I didn’t put '# it wouldn’t work since turns out it was doing 2 different requests to 2 different databases. This allowed me to leak the Event Registrations database, and I found other fields like me_firstname, me_lastname, me_phonenumber, me_companyname, me_country, and more! I could leak all of the fields in the form:

image

(Some events have extra custom fields like Partner ID, Tenant ID, and more)

Here’s a diagram I made:

image

(Targeting each database individually works because the injections are in different orders)

So I could leak 2 entire Microsoft databases (Event Registration and Waitlist) which both had full names, emails, phone numbers, and more. I tried editing the MSRC report but realized I couldn’t, so I left a comment instead.

Shouts out to zlz/Sam Curry and Rhynorater for their “Hacking Starbucks” writeup.

Another secondary context vulnerability!

Timeline:

> Star or leave a comment on GitHub! (or follow me)