How I Could've Accessed 17 Trillion Microsoft Records

An estimated 17.3 trillion stored rows across a wide range of Microsoft datasets were reachable through a single internal analytics service, all because it never checked the signature on a login token. That flaw let me claim an administrator’s identity and submit unauthorized SQL queries without any real credentials. I used only table descriptions, metadata, and bounded sample rows to understand the potential scope.

Two quick notes first. The impact I describe is hypothetical. It’s what an attacker could have done with this access, but luckily I found the bug instead, reported it, and never touched any customer data or PII. And for transparency: Microsoft had editorial control over this post, cutting sections and figures and reshaping how the impact is described before publication.

Microsoft said the following about this finding:

“We appreciate the opportunity to investigate the findings reported by Faav. Their submission and coordinated vulnerability disclosure helped us to better protect our customers by hardening our services. We value and appreciate safe security research under the terms of the Microsoft Bug Bounty Program and look forward to continuing to work with Faav in the future.”

Hey! I’m Faav. A little over a year ago, when I was 15, I published Break into any Microsoft building: Leaking PII in Microsoft Guest Check-In, my first Microsoft write-up. I’m 16 now, and this one is a little bigger.

Since then I’ve gone all-in on bug bounty. I’ve spent the year hacking Microsoft off and on around school, and finding bugs across Amazon, Google, Adobe, and a bunch of other companies. I also started building AI into how I hunt, which led me to develop Antares, my personal AI hackbot.

This one started as an automated lead that Antares couldn’t finish. Ten days later, after a Friday of schoolwork and one late-night hunch, it turned into the biggest Microsoft bug I’d ever found.

Finding the Titan API

On August 25, 2026, Antares identified an internal Microsoft service called Titan. Its web interface sat behind a VPN REQUIRED page for Microsoft employees, so the frontend was out of reach. But since when has a locked front door stopped anyone?

image

The “VPN REQUIRED” page shown to a non-employee visiting Titan’s frontend.

The API wasn’t linked anywhere on the frontend, so Antares searched Microsoft subdomains and found a separate endpoint that resolved to an Azure Cloud Services host. Its public Swagger file listed four routes:

/GetConfiguration
/GetOnboardedTables
/v2/Query
/v2/Insert

The Swagger doc specified Azure AD bearer authentication for three of the four routes. The exception was /v2/Query, which also happened to be the one that accepted raw SQL. So naturally, that’s where I started poking.

The query needed a tableName, and Swagger gave no example values. I pulled 2023 snapshots of Titan’s login and privacy pages from the Wayback Machine, and reading the archived Superset configuration recovered 56 table definitions, including a routing value called TestData.

image

The archived Titan interface before the current VPN restriction.

I tried that value against the live API:

POST /v2/Query HTTP/1.1
Host: [redacted]
Content-Type: application/json

{"query":"SELECT 1","tableName":"TestData","rowLimit":1}

With no authorization header it returned 401 Unauthorized, so Antares started probing how it validated JWTs.

Breaking the JWT

Over the next ten days, while I worked through hundreds of other leads, Antares kept coming back to Titan and chipping away at its JWT checks one error at a time.

It started with a token from my external Entra test tenant, created months earlier and used regularly for testing. Titan threw back a tenant error. Changing the tenant to Microsoft’s reached an audience error. Changing the audience hit an application allowlist error. Changing the application ID finally reached a user lookup.

The payload kept changing while the signature stayed exactly the same, and Titan kept accepting the new claims, like a bouncer checking the name on every ID but never looking at the photo. That was the first big clue it wasn’t verifying signatures.

I’d exploited an unsigned JWT bypass by hand before I ever used AI, so I recognized the pattern immediately.

Next I replaced the token entirely with a synthetic JWT using this header:

{"alg":"none","typ":"JWT"}

A normal signed JWT has three populated sections: header.payload.signature. Mine ended with a bare period, because the third section was empty:

base64url(header).base64url(payload).

Titan wasn’t validating the signature at all.

The payload used the values Titan expected, but with a upn I controlled:

{
  "aud": "[redacted]",
  "tid": "[redacted]",
  "appid": "[redacted]",
  "upn": "test@microsoft.com",
  "oid": "00000000-0000-0000-0000-000000000000"
}

That cleared the tenant, audience, and application checks, then returned:

User 'test@microsoft.com' not found

Antares was running Codex and Claude on the lead. Because a UPN is normally an email-formatted Entra identity, both models kept testing placeholders, published service aliases, and Microsoft employee-style addresses.

The unsigned token was already reaching Titan’s local user lookup. Antares just couldn’t find a UPN Titan recognized, so it had no working query and no impact to show, and the finding stayed a lead.

Trying admin

I’d spent Friday on schoolwork. By the time the Titan lead popped up again and I decided to take another look myself, it was after 1 AM on Saturday, September 5.

I tried a few valid-looking UPNs and they failed too. So I stopped guessing and started thinking about what the backend was actually doing with the claim. What if it was using upn to look up a local application username? I changed the unsigned token’s upn from an email-formatted identity to admin.

The result was only the number 1, but after ten days of authentication errors, it was a very interesting 1. I was finally executing SQL as Titan’s administrator.

image

Titan accepted the unsigned administrator identity and executed the SQL query.

The funny part is that admin is obvious, but obviously not a valid UPN. That’s exactly why Antares never guessed it. I only tried it because I stopped taking the field name at face value and thought about what a developer might have done on the backend.

Titan used the unsigned upn claim as a local username. admin resolved to local user ID 1, which held the Admin role, and the SQL ran. Sometimes the answer really is just admin.

Inside the Databases

I tried TestData first, assuming it would only hold fake data, and it did: test cases in a test database. But SHOW DATABASES revealed the rest, including Titan’s platform metadata database. From there I could query application tables directly.

The metadata database held the real data. The first thing I found was a table of active application user accounts with names, email addresses, and login history. Across the platform metadata:

image

An application user record with identifying information and login activity. The password field held a placeholder hash from Superset’s local user model, not a real Microsoft credential. Sensitive values are redacted.

The Titan user and usage directory exposed employee details such as job titles, departments, and management hierarchy for staff associated with Titan. This data only covered a subset of Microsoft employees, not the full employee directory. It could’ve helped an attacker craft targeted social-engineering attempts, though I never tested or demonstrated that.

At that point the employee data looked like the main finding. Then I noticed a separate Bing analytics source.

Validating a Bounded Bing Analytics Sample

I requested one row from the latest available Bing analytics partition. A second one-row query returned a different record from the same partition. These bounded samples showed that Bing search analytics were reachable through the service. I limited my testing to two one-row samples. Bing was only one of the databases reachable this way.

image

One analytics record containing search, identifier, and high-level location fields. My query pulled only a few of the available fields. Note that the location values didn’t contain precise user locations, but rather country or state-level information from reverse IP. Sensitive values are redacted.

Because MUIDs appeared in more than one dataset, it’s plausible that user activity could’ve been correlated across services, though I never actually did this. I didn’t identify anyone, link records across datasets, or build any profile from what I sampled.

I’d started drafting a report to MSRC as soon as I found the employee records. After seeing the Bing data I reported it immediately.

17.3 Trillion Rows

The total scale was the last thing I worked out.

I tested all 56 routing values from the archived config with SELECT 1. 30 were still active. Each routing value pointed to a backend configuration, and each configuration contained one or more databases, so the 30 live values resolved through 24 configurations to 17 connected analytics databases spanning 9,863 unique table names.

There was no convenient grand total. I had separate metadata counts from 17 ClickHouse databases, so I handed them to the AI to sum while I checked how each Distributed table mapped to its backing cluster. I counted one replica per shard and verified the total through two metadata paths: system.tables.total_rows and active system.parts.

The sum came back unformatted:

17333335124315

I had to split it into groups of three just to read it:

17,333,335,124,315

Seventeen trillion - a storage estimate from metadata that likely includes historical, duplicated, and derived data, but quite the high number nonetheless. That was the estimated scale of the environment technically reachable through this bypass.

My first thought was that the replicas had been double-counted, or a few extra zeroes had slipped in. I checked again. They hadn’t. Both paths returned the same number.

It was 2 AM. I wanted to yell, or at least say something out loud, but my parents were asleep. So I just sat there staring at 17,333,335,124,315 and checked the math again.

Closing Thoughts

There are two things I took away from this.

First, AI and human intuition compounded here. Antares did ten days of work I didn’t have to: enumerating subdomains, grinding through JWT error messages one field at a time, mapping the whole attack surface, and getting the unsigned token through four layers of validation. What it couldn’t do was realize that upn wasn’t actually a UPN. Looking back, the User not found error should’ve been the tell. The crafted JWT had already passed Titan’s authentication checks, and it was simply trying to match upn to one of its own users. Antares wouldn’t have gotten here alone, and neither would I. Its persistence, plus one human hunch, is what made this find possible.

Second, the bug. Titan validated the contents of the JWT (tenant, audience, app ID, user) but never verified the signature, the most important part of any authentication check. The authentication checks felt like a hotel where every door had a working keycard reader, but any keycard unlocked any room. Despite all the access-control logic existing in the app, the one missing piece made it all pointless. If you’re a developer (or coding agent) reading this, the most important takeaway from this post is to make sure you verify signatures above all else when building auth.

Timeline