Hoe ik toegang had kunnen krijgen tot 17 biljoen Microsoft-records
Twee belangrijke kanttekeningen vooraf: de impact die ik beschrijf is hypothetisch. Dit is wat een aanvaller had kunnen doen met deze toegang, maar gelukkig heb ik de bug gevonden, deze gemeld en nooit klantgegevens of PII (persoonlijk identificeerbare informatie) geraakt. Daarnaast, in het interest van transparantie: Microsoft had redactionele controle over dit bericht; zij hebben secties en figuren geschrapt en de beschrijving van de impact aangepast vóór publicatie.
Microsoft reageerde als volgt op deze bevinding:
"We waarderen de gelegenheid om de bevindingen die door Faav zijn gerapporteerd te onderzoeken. Hun indiening en gecoördineerde openbaarmaking van de kwetsbaarheid hebben ons geholpen onze klanten beter te beschermen door onze diensten te versterken. We waarderen veilig security-onderzoek onder de voorwaarden van het Microsoft Bug Bounty Program en kijken ernaar uit om in de toekomst met Faav te blijven samenwerken."
Ik ben Faav. Iets meer dan een jaar geleden, toen ik 15 was, publiceerde ik mijn eerste Microsoft write-up: Break into any Microsoft building: Leaking PII in Microsoft Guest Check-In. Inmiddels ben ik 16 en is deze vondst iets groter.
Sinds die tijd ben ik volledig gefocust op bug bounty. Ik heb het afgelopen jaar af en toe Microsoft gehackt naast mijn schoolwerk, en bugs gevonden bij Amazon, Google, Adobe en diverse andere bedrijven. Ik ben ook begonnen met het integreren van AI in mijn zoektocht, wat leidde tot de ontwikkeling van Antares, mijn persoonlijke AI-hackbot.
Deze specifieke bug begon als een geautomatiseerde aanwijzing die Antares niet kon voltooien. Tien dagen later, na een vrijdag vol schoolwerk en een vermoeden laat op de avond, groeide het uit tot de grootste Microsoft-bug die ik ooit had gevonden.
De Titan API ontdekken
Op 25 augustus 2026 identificeerde Antares een interne Microsoft-dienst genaamd Titan. De webinterface bevond zich achter een pagina met de melding "VPN REQUIRED" voor Microsoft-medewerkers, waardoor de frontend onbereikbaar was. Maar een gesloten voordeur houdt een onderzoeker niet tegen.
De API was nergens op de frontend gekoppeld, dus zocht Antares naar Microsoft-subdomeinen en vond een apart eindpunt dat resolveerde naar een Azure Cloud Services-host. Het publieke Swagger-bestand somde vier routes op:
/GetConfiguration/GetOnboardedTables/v2/Query/v2/Insert
De Swagger-documentatie specificeerde Azure AD bearer-authenticatie voor drie van de vier routes. De uitzondering was /v2/Query, die toevallig ook raw SQL accepteerde. Dat was dus het startpunt.
De query had een tableName nodig, maar Swagger gaf geen voorbeeldwaarden. Ik haalde snapshots uit 2023 van de login- en privacypagina's van Titan op via de Wayback Machine. Door de gearchiveerde Superset-configuratie te lezen, herstelde ik 56 tabeldefinities, waaronder een routingwaarde genaamd TestData.
Ik testte deze waarde tegen de live API:
POST /v2/Query HTTP/1.1
Host: [redacted]
Content-Type: application/json
{"query":"SELECT 1","tableName":"TestData","rowLimit":1}
Zonder autorisatie-header returnede de server 401 Unauthorized, waarna Antares begon met het onderzoeken van hoe de JWT's (JSON Web Tokens) werden gevalideerd.
Het JWT doorbreken
Gedurende de volgende tien dagen, terwijl ik honderden andere leads afwerkte, bleef Antares terugkomen bij Titan en de JWT-controles één voor één afbreken.
Het begon met een token van mijn externe Entra-testtenant, die maanden eerder was aangemaakt. Titan gaf een tenant-fout. Door de tenant te wijzigen naar die van Microsoft, ontstond een audience-fout. Het wijzigen van de audience leidde tot een application allowlist-fout. Pas na het wijzigen van de applicatie-ID werd er een gebruikerscontrole (user lookup) uitgevoerd.
De payload veranderde voortdurend terwijl de handtekening exact hetzelfde bleef, en Titan bleef de nieuwe claims accepteren—als een uitsmijter die de naam op elk identiteitsbewijs controleert, maar nooit naar de foto kijkt. Dat was de eerste grote aanwijzing dat de handtekeningen niet werden geverifieerd.
Ik had eerder handmatig een unsigned JWT-bypass geëxploiteerd, dus ik herkende het patroon onmiddellijk. Vervolgens verving ik het token volledig door een synthetische JWT met deze header:
{"alg":"none","typ":"JWT"}
Een normale ondertekende JWT heeft drie secties: header.payload.signature. Mijn token eindigde met een enkele punt, omdat de derde sectie leeg was: base64url(header).base64url(payload).
Titan valideerde de handtekening helemaal niet. De payload gebruikte de waarden die Titan verwachtte, maar met een upn die ikzelf controleerde:
{
"aud": "[redacted]",
"tid": "[redacted]",
"appid": "[redacted]",
"upn": "[email protected]",
"oid": "00000000-0000-0000-0000-000000000000"
}
Dit omzeilde de tenant-, audience- en applicatiechecks, maar resulteerde in: User '[email protected]' not found.
Antares gebruikte Codex en Claude voor deze lead. Omdat een UPN normaal gesproken een e-mailformaat Entra-identiteit is, bleven beide modellen placeholders, gepubliceerde service-aliassen en adressen in de stijl van Microsoft-medewerkers testen. De onondertekende token bereikte al de lokale gebruikerscontrole van Titan, maar Antares kon simpelweg geen UPN vinden die Titan herkende. De vondst bleef daarom een 'lead' zonder bewezen impact.
De 'admin' poging
Na een vrijdag vol schoolwerk besloot ik op zaterdag 5 september, na 01:00 uur 's nachts, de Titan-lead zelf opnieuw te bekijken.
Ik probeerde een paar geloofwaardige UPN's, maar ook die faalden. Ik stopte met gissen en begon na te denken over wat de backend precies met de claim deed. Wat als het de upn gebruikte om een lokale applicatie-gebruikersnaam op te zoeken? Ik veranderde de upn in het onondertekende token van een e-mailformaat identiteit naar admin.
Het resultaat was simpelweg het getal 1, maar na tien dagen van authenticatiefouten was dit een zeer interessante 1. Ik voerde nu SQL uit als de beheerder van Titan.
Titan accepteerde de onondertekende beheerder-identiteit en voerde de SQL-query uit. Het grappige is dat admin voor de hand ligt, maar overduidelijk geen geldige UPN is. Dat is precies waarom Antares het nooit had geraden. Ik probeerde het alleen omdat ik stopte met het letterlijk nemen van de veldnaam en nadacht over wat een ontwikkelaar aan de backend zou kunnen hebben gedaan.
Titan gebruikte de onondertekende upn-claim als lokale gebruikersnaam. admin resolveerde naar lokale gebruikers-ID 1, die de Admin-rol had, en de SQL werd uitgevoerd. Soms is het antwoord simpelweg admin.
Inzicht in de databases
Ik begon met TestData, in de veronderstelling dat dit alleen testdata zou bevatten, wat ook zo was: testcases in een testdatabase. Maar SHOW DATABASES onthulde de rest, inclusief de platform-metadata-database van Titan. Vanuit daar kon ik applicatietabellen direct bevragen.
De metadata-database bevatte de echte data. Het eerste dat ik vond was een tabel met actieve applicatie-gebruikersaccounts met namen, e-mailadressen en login-historie. Binnen de platform-metadata vond ik:
- Ongeveer 25.000 account- en e-mailrecords.
- 17.990 e-mailrecords van medewerkers.
- 15.001 organisatierecords van medewerkers.
- 355 databaseconfiguraties.
- 20.979 SQL-definities van virtuele datasets.
- 24.569 dashboards, 425.891 grafieken en 27.347 datasetdefinities.
De Titan-gebruikers- en gebruiksdirectory legde details van medewerkers bloot, zoals functietitels, afdelingen en managementhiërarchie voor personeel dat geassocieerd was met Titan. Deze data besloeg slechts een subset van Microsoft-medewerkers, niet de volledige personeelsgids. Dit had een aanvaller kunnen helpen bij het opstellen van gerichte social-engineering-pogingen, hoewel ik dat nooit heb getest of gedemonstreerd.
Op dat moment leken de gegevens van de medewerkers de belangrijkste vondst. Toen merkte ik een aparte Bing analytics-bron op.
Validatie van een beperkte steekproef uit Bing Analytics
Ik vroeg één rij op uit de nieuwste beschikbare Bing analytics-partitie. Een tweede query van één rij leverde een ander record op uit dezelfde partitie. Deze beperkte steekproeven toonden aan dat Bing search-analytics bereikbaar waren via de dienst. Ik beperkte mijn testen tot twee steekproeven van één rij. Bing was slechts één van de databases die op deze manier bereikbaar waren.
Omdat MUID's (Microsoft Unique Identifiers) in meer dan één dataset verschenen, is het aannemelijk dat gebruikersactiviteit over diensten heen gecorreleerd had kunnen worden, hoewel ik dit nooit daadwerkelijk heb gedaan. Ik heb niemand geïdentificeerd, records tussen datasets niet gekoppeld en geen profiel opgesteld op basis van de steekproeven.
Ik was al begonnen met het opstellen van een rapport voor MSRC toen ik de medewerkersrecords vond. Na het zien van de Bing-data heb ik het onmiddellijk gerapporteerd.
17,3 biljoen rijen
De totale omvang was het laatste dat ik uitzocht.
Ik testte alle 56 routingwaarden uit de gearchiveerde configuratie met SELECT 1. Hiervan waren er 30 nog actief. Elke routingwaarde wees naar een backend-configuratie, en elke configuratie bevatte één of meer databases. De 30 actieve waarden resolveerden via 24 configuraties naar 17 gekoppelde analytics-databases, verdeeld over 9.863 unieke tabelnamen.
Er was geen handig totaaloverzicht. Ik had aparte metadata-tellingen van 17 ClickHouse-databases, dus ik liet de AI deze optellen terwijl ik controleerde hoe elke Distributed tabel was gekoppeld aan zijn backing cluster. Ik telde één replica per shard en verifieerde het totaal via twee metadata-paden: system.tables.total_rows en actieve system.parts.
De som kwam ongeformatteerd terug: 17333335124315.
Ik moest het in groepen van drie splitsen om het te kunnen lezen: 17.333.335.124.315.
Zeventien biljoen—een schatting op basis van metadata die waarschijnlijk historische, gedupliceerde en afgeleide data bevat, maar desondanks een enorm getal. Dat was de geschatte omvang van de omgeving die technisch bereikbaar was via deze bypass.
Mijn eerste gedachte was dat de replica's dubbel waren geteld of dat er een paar nullen te veel waren geslopen. Ik controleerde het opnieuw. Dat was niet het geval. Beide paden gaven hetzelfde getal terug.
Het was 02:00 uur 's nachts. Ik wilde schreeuwen, of in ieder geval iets hardop zeggen, maar mijn ouders sliepen. Dus zat ik daar maar te staren naar 17.333.335.124.315 en controleerde ik de berekening nogmaals.
Slotbeschouwingen
Ik trek hier twee conclusies uit.
Ten eerste: AI en menselijke intuïtie versterkten elkaar hier. Antares deed tien dagen werk dat ik niet hoefde te doen: subdomeinen enumereren, JWT-foutmeldingen veld voor veld afwerken, het gehele aanvalsoppervlak in kaart brengen en de onondertekende token door vier lagen van validatie krijgen. Wat het niet kon, was beseffen dat upn niet daadwerkelijk een UPN was. Terugkijkend had de foutmelding User not found de aanwijzing moeten zijn. De vervalste JWT was al door de authenticatiechecks van Titan gekomen, en het systeem probeerde simpelweg de upn te matchen met een van zijn eigen gebruikers. Antares zou dit niet alleen hebben gehaald, en ik ook niet. De persistentie van de AI, gecombineerd met een menselijk vermoeden, maakte deze vondst mogelijk.
Ten tweede: de bug. Titan valideerde de inhoud van de JWT (tenant, audience, app ID, gebruiker), maar verifieerde nooit de handtekening—het belangrijkste onderdeel van elke authenticatiecheck. De authenticatiechecks leken op een hotel waar elke deur een werkende sleutelkaartlezer had, maar waar elke sleutelkaart elke kamer ontsloot. Ondanks dat alle toegangscontrole-logica in de app bestond, maakte het ontbreken van dit ene onderdeel alles zinloos. Als je een ontwikkelaar (of coding agent) bent die dit leest: de belangrijkste les is dat je altijd, boven alles, de handtekeningen verifieert bij het bouwen van authenticatie.
Tijdlijn
- 25-08-2026: Antares ontdekte de publieke API van Titan en sloeg deze op als lead. Ik herstelde de gearchiveerde Superset-configuratie en de 56 tabeldefinities via de Wayback Machine.
- 25-08-2026 – 05-09-2026: Antares werkte herhaaldelijk de JWT-checks van Titan af en testte UPN's in e-mailformaat.
- 05-09-2026: Ik nam de request over, veranderde
upnnaaradminen kreeg de SQL-query werkend. - 05-09-2026: Bevestigde toegang tot 30 actieve routing-doelen en 17 gekoppelde analytics-databases zonder de onderliggende records te dumpen, en meldde de kwetsbaarheid bij MSRC. Case 144051 werd dezelfde dag geopend.
- 06-09-2026 – 08-09-2026: MSRC vroeg mij om te stoppen met testen en vroeg om mijn IP-adres om te bevestigen dat er geen activiteit was buiten mijn security-onderzoek. Ze bevestigden dat het probleem met prioriteit werd behandeld.
- 09-09-2026: Het API-eindpunt werd afgesloten. MSRC gaf aan dat het rapport leidde tot onmiddellijk onderzoek en herstel om de resterende blootstelling aan te pakken.
- 17-09-2026: Ontvangst van een bounty van $5.000.
- 22-09-2026: Overleg met Microsoft om de bevinding te bespreken en de openbaarmaking te coördineren.
- 22-09-2026 – 24-09-2026: Herschrijven van deze write-up op verzoek van Microsoft, waarbij de impact is aangepast en sommige secties en figuren zijn verwijderd voorafgaand aan publicatie.
Groetjes,