Data-only aanvallen zijn makkelijker dan u denkt

Data-only aanvallen — aanvallen die de control-flow (de volgorde van uitvoering) van een programma niet beïnvloeden — zijn lange tijd beschouwd als te geavanceerd en niche om een praktisch gevaar te vormen. Met ons onderzoek hebben we echter een tool gebouwd die deze aanvallen met verrassende eenvoud automatisch genereert. We leggen uit hoe dergelijke aanvallen werken en waarom onze tool, Einstein, zowel onderzoekers als leveranciers oproept om hun mitigatiestrategieën te heroverwegen.

Stel dat u een hacker bent en u heeft een bug gevonden waarmee u gegevens in een slachtofferprogramma kunt overschrijven. Een dergelijk scenario is niet ongewoon: Microsoft, Google en Mozilla melden dat ongeveer 70% van hun beveiligingsbugs inderdaad van dit type geheugentekortkomingen (memory safety bugs) zijn [1, 2, 3]. De vraag is dan, als hacker, hoe u deze bug omzet in een echt exploit.

In het verleden zou dit relatief eenvoudig zijn geweest: u zou de bug bijvoorbeeld kunnen gebruiken voor een control-flow hijacking-aanval, waarbij u codepointer-adressen in het programma overschrijft [4] om het programma te dwingen uw eigen kwaadaardige code uit te voeren. Vanwege decennia aan onderzoek (resulterend in verdedigingsmechanismen zoals DEP, CFI, CPI, etc.) is het echter nu zeer moeilijk om de control-flow van een programma af te buigen van de code die het oorspronkelijk beoogde uit te voeren. Hierdoor is het in de praktijk vaak onuitvoerbaar om de bug op deze manier te weaponizen.

In ons onlangs gepubliceerde artikel op USENIX Security 2024 [5] presenteren we een praktische aanpak voor een geheel andere methode van exploitatie: het programma alle beoogde code laten uitvoeren (bijv. alle onschuldige functies, systeemoproepen, etc.), maar met kwaadaardige data. Deze zogenaamde data-only aanvallen zijn al langer bekend [6], maar er werd aangenomen dat ze te applicatiespecifiek of te complex waren om een praktisch gevaar te vormen [7]. In ons werk tonen we aan dat deze aannames niet gerechtvaardigd zijn. In het bijzonder hebben we een schaalbare en geautomatiseerde oplossing geïmplementeerd, Einstein, die aantoont dat het bouwen van data-only aanvallen eenvoudig is — en ruim binnen het bereik van aanvallers met weinig middelen. In dit artikel bespreken we de inzichten die Einstein in staat stellen om dergelijke exploits automatisch en met verrassende eenvoud te genereren, en de implicaties van onze bevindingen voor softwareleveranciers.

Een voorbeeld van een data-only aanval

Laten we eerst een van de klassieke data-only aanvallen uit de literatuur behandelen, die een webserver als slachtoffer heeft [6] (vereenvoudigd voor de duidelijkheid). Bij het opstarten leest de server zijn configuratiebestand om zijn gegevens te initialiseren. Een van deze configuratie-opties is het CGI-BIN-pad, de directory die wordt gebruikt om externe programma's uit te voeren. In ons voorbeeld stelt de server de variabele cgibinpath in op /usr/local/server/cgi-bin. We nemen aan dat de server een programma in zijn CGI-BIN-directory heeft, sortscript, dat een cliënt kan gebruiken om getallen te sorteren. Bovendien heeft de server een memory safety bug waardoor een kwaadaardige cliënt een buffer kan laten overlopen en bijvoorbeeld de inhoud van de cgibin_path-variabele kan overschrijven naar /bin.

(Figuur 1: Een voorbeeld van een memory safety bug die de aanvaller in staat stelt het CGI-BIN-pad te wijzigen.)

Hoewel de low-level details van de bug kunnen variëren (bijvoorbeeld een use-after-free in plaats van een buffer overflow), rijst de vraag: hoe kan een aanvaller een dergelijke bug weaponizen? Om dit te beantwoorden, laten we eerst zien hoe de server interacteert met een onschuldige cliënt, en daarna met een kwaadaardige cliënt.

(Figuur 2: Een voorbeeld van een data-only aanval die het CGI-BIN-pad van een server corrumpeert om willekeurige code uit te voeren.)

Nadat de server is geïnitialiseerd (Fig. 2a), begint hij verzoeken te verwerken. Een onschuldige cliënt interacteert als volgt (Fig. 2b):

  1. De cliënt stuurt een "POST sort-script" verzoek met de ongesorteerde getallen "2 1 3" in het request body.
  2. De server voegt het CGI-BIN-pad en het pad van het verzoek samen om het programma te bepalen dat moet worden uitgevoerd: /usr/local/server/cgi-bin/sort-script.
  3. De server voert het programma (het sorteerscript) uit en geeft "2 1 3" als input. Dit gebeurt door de execve systeemoproep (syscall) aan te roepen, die het besturingssysteem instrueert het script namens de server uit te voeren.
  4. Het script sorteert de getallen en geeft "1 2 3" terug, wat de server in zijn HTTP-respons naar de cliënt doorstuurt.

Nu schetsen we hoe een kwaadaardige cliënt dit kan exploiteren (Fig. 2c):

  1. De cliënt exploiteert de bug om cgibinpath in te stellen op de string /bin (Fig. 1).
  2. De cliënt stuurt een "POST /sh" verzoek met touch /tmp/attacker-was-here in het request body.
  3. De server voegt het CGI-BIN-pad en het pad van het verzoek samen om het programma te bepalen dat moet worden uitgevoerd: /bin/sh.
  4. De server voert het programma, in dit geval de systeem-shell, uit en geeft touch /tmp/attacker-was-here als input. Ook hier gebeurt dit via de execve systeemoproep.
  5. De shell maakt het bestand /tmp/attacker-was-here aan.

Er zijn hier twee belangrijke conclusies te trekken:

  • Ten eerste voert de servers geen kwaadaardige code uit die door de cliënt is aangeleverd; alle schadelijke acties worden getriggerd door kwaadaardige data. De aanval wijzigt effectief alleen de argumenten van de execve syscall. Op andere punten zijn de onschuldige en kwaadaardige executies gelijkwaardig — bij het verwerken van een verzoek voert de server dezelfde stappen en functies uit, zij het met andere argumenten.
  • Ten tweede is deze aanval zeer krachtig, omdat het de aanvaller in staat stelt willekeurige programma's op de machine van het slachtoffer uit te voeren. In ons voorbeeld maakt de cliënt alleen een bestand aan, maar elk shell-commando is mogelijk, zoals het installeren van malware of het exfiltreren van data.

Waarom data-only aanvallen als moeilijk worden beschouwd

Ondanks de ontdekking van data-only aanvallen bijna twee decennia geleden, stelt de gangbare opinie dat ze zelden een praktisch gevaar vormen, omdat ze ofwel te applicatiespecifiek of te complex zouden zijn.

Applicatiespecifiek. Zoals de auteurs van de voorbeeldaanval opmerkten, vereist het bouwen van zo'n aanval "geavanceerde kennis over de programmasemantiek". Met andere woorden, een aanvaller moet zo bekend zijn met de interne werking van de server — via reverse-engineering van de code of het bestuderen van protocollen — dat zij weten dat specifiek de cgibinpath-variabele beveiligingskritisch is, en dat een specifiek vervormd POST-verzoek dit kan exploiteren. Men gaat ervan uit dat deze arbeidsintensieve analyse te duur is en dat dergelijke aanvallen daarom te niche zijn.

Complex. Recente benaderingen voor het bouwen van data-only aanvallen betreden complex terrein, vanuit de aanname dat simpelere aanvallen (zoals het voorbeeld) niet algemeen haalbaar zijn. Men neemt aan dat men ofwel complexe data-flow constraints moet oplossen met zware analyses, of het programma moet afbuigen van de bedoelde code om verdedigingsmechanismen te omzeilen. Sommige benaderingen gaan zelfs zo ver dat ze hoogcomplex, Turing-complete machines construeren — iets wat echte aanvallers zelden nodig hebben.

In ons werk tonen we aan dat deze aannames onjuist zijn: exploitatie vereist geen uitgebreide kennis van de programmasemantiek, het oplossen van complexe data-flow constraints, of het op een gecompliceerde manier afbuigen van de control-flow.

Einstein: "Zo simpel mogelijk, maar niet simpeler"

Geïnspireerd door het citaat dat aan Albert Einstein wordt toegeschreven, presenteren we een eenvoudige (maar niet té eenvoudige) pipeline voor data-only exploitatie, genaamd Einstein. Einstein genereert aanvallen met een applicatie-agnostische techniek, wat bewijst dat dergelijke aanvallen ruim binnen het bereik van aanvallers met weinig middelen liggen.

Applicatie-agnostisch. In plaats van te proberen applicatiespecifieke semantiek te begrijpen (zoals de details van het HTTP-protocol), richt Einstein zich op een universele interface die elk programma gebruikt om met de kernel van het besturingssysteem te communiceren: de systeemoproepen (syscalls). In het bijzonder volgen we de data die terechtkomt in syscall-argumenten, om te bepalen of een aanvaller deze kan corrumperen om bijvoorbeeld willekeurige code uit te voeren via execve of bestanden in het bestandssysteem te wijzigen via write.

Simpel. Bovendien abstraheert Einstein onnodige complexiteit weg en richt hij zich op de exploits die niet alleen het eenvoudigst te identificeren zijn, maar ook het meest veelbelovend voor een aanvaller. Einstein genereert automatisch exploits voor beveiligingsgevoelige syscalls langs een (reeds geldige) runtime-pad van een programma, waarvan de argumenten simpelweg letterlijk (verbatim) zijn gekopieerd van data die door de aanvaller beheerst kan worden. Deze eenvoudige aanpak kan automatisch een verrassend groot aantal praktische data-only exploits genereren in populaire real-world programma's.

Hoe Einstein de voorbeeldaanval bouwt

Om uit te leggen hoe Einstein werkt, lopen we door de stappen waarmee hij de voorbeeldaanval bouwt. We nemen aan dat de aanvaller toegang heeft tot een programma dat gelijk is aan dat van het slachtoffer, zodat de server lokaal voor analyse kan worden gedraaid. Einstein neemt het programma als input en werkt in twee fasen: eerst het genereren van kandidaat-exploits, en daarna het bevestigen of elke kandidaat daadwerkelijk werkt.

1. Genereren van kandidaat-exploits

Einstein volgt tijdens runtime alle data die door een aanvaller gecorrumpeerd kan worden, om te bepalen welke data de argumenten van beveiligingsgevoelige syscalls kan beïnvloeden.

  • Instrumentatie: De server wordt gestart met binary-level instrumentatie (Fig. 3a, ➊). Deze voegt ondersteuning toe voor dynamic taint analysis, waardoor we "besmette" (tainted) programmadata tijdens runtime kunnen volgen [8].
  • Tainting: Terwijl de server opstart en zijn cgibinpath initialiseert, modelleert Einstein een aanvaller door alle data die potentieel gecorrumpeerd kan worden (zoals de string /usr/local/server/cgi-bin) uniek te "besmetten" (➋).
  • Propagatie: Einstein volgt hoe deze besmette data zich verspreidt terwijl de server een workload van onschuldige verzoeken verwerkt (➌). Wanneer de server bijvoorbeeld een "POST /sort-script" verzoek afhandelt, geeft hij de besmette string als argument door aan de execve syscall. Einstein identificeert deze stroom van aanvallers-controleerbare data naar een gevoelige syscall (<0xE2><0x9E><0x8D>).
  • Identity Data Flow: Einstein stelt vast dat de pathname en argv parameters van execve niet alleen besmet zijn, maar in feite identiek zijn aan cgibinpath. Dit noemen we een identity data flow. Einstein bouwt een kandidaat-exploit door (adres, waarde) paren te genereren die specificeren dat de geheugen-write bug de execve kan exploiteren door cgibinpath te wijzigen van /usr/local/server/cgi-bin naar /bin (➎).

(Figuur 3a: Eerst genereert Einstein een kandidaat-exploit.)

2. Bevestiging van de exploit

Een identity data flow is niet automatisch exploiteerbaar. De server zou bijvoorbeeld kunnen controleren of het bestand zich in een vooraf ingestelde directory bevindt voordat het wordt uitgevoerd. Daarom bevestigt Einstein of de kandidaat-exploit werkt.

  • Herstart: De server wordt opnieuw gestart (Fig. 3b, <0xE2><0x9E><0x8F>).
  • Overschrijven: Op het punt waar een aanvaller de memory write bug zou gebruiken, overschrijft Einstein de data zoals gespecificeerd in de kandidaat-exploit, waardoor cgibinpath verandert in /bin (<0xE2><0x9E><0x90>).
  • Triggeren: Er wordt een workload gestuurd om de gadget te exploiteren — in dit geval een "POST /sh" verzoek met een shell-commando om een bestand aan te maken (<0xE2><0x9E><0x91>).
  • Verificatie: Einstein bevestigt dat het bestand daadwerkelijk is aangemaakt, waarmee de exploit is bewezen (<0xE2><0x9E><0x92>).

(Figuur 3b: Daarna bevestigt Einstein dat de kandidaat-exploit een werkende exploit is.)

Evaluatie

We hebben gezien hoe Einstein de voorbeeldaanval van bijna twintig jaar geleden kan bouwen. We hebben dit vervolgens getest op populaire moderne servers: de webservers httpd, lighttpd en nginx, en de databaseservers postgres en redis.

Tabel 1 toont het aantal besmette syscalls per programma en het percentage daarvan met een identity data flow.

Tabel 1: Aantal door aanvallers besmette syscalls per doelprogramma

DoelprogrammaBeveiligingsgevoelige syscalls met besmette argumenten (% met identity data flow)Code-dekking
httpd1834 (97%)27.3%
lighttpd92 (98%)27.8%
nginx1623 (82%)49.1%
postgres2105 (27%)46.5%
redis218 (84%)33.6%

We zien dat een aanvaller veel gevoelige syscall-argumenten kan corrumperen. Met uitzondering van postgres zorgt het hoge percentage identity flows (84–98%) ervoor dat we voor de overgrote meerderheid kandidaat-exploits kunnen genereren. Ondanks de relatief lage code-dekking (27–49%) ontdekt Einstein al veel problemen.

Voor nginx hebben we de kandidaat-exploits verder bevestigd. Tabel 2 toont de gevonden primitives die beschikbaar zijn voor een aanvaller.

Tabel 2: Bevestigde exploits voor nginx

Attack PrimitiveAantal
Code-Execution1
Write-What-Where17
Write-What375
Write-Where79
Send-What-Where41
Send-What372
Send-Where59
Totaal944

Ondanks de heersende mening dat data-only aanvallen te complex of niche zijn, concluderen we dat een aanvaller vandaag de dag nog steeds over een diverse set primitives beschikt tegen populaire serverprogramma's.

Conclusie

We hebben Einstein gepresenteerd, een pipeline voor data-only exploitatie die automatisch exploits bouwt tegen populaire servers en moderne mitigaties omzeilt. De vraag is: hoe kunnen we deze aanvallen effectief bestrijden? Een goede mitigatie moet aan twee eisen voldoen: (1) volledigheid (comprehensiveness), het volledig afsluiten van het aanvalsoppervlak, en (2) praktisch gemak (practicality), zodat het eenvoudig te implementeren is.

Bij oude control-flow hijacking aanvallen waren de parameters duidelijk: men overschreef een codepointer die een indirecte vertakking (zoals een return instructie) corrumpeerde. Hierdoor konden mitigaties zoals DEP, CFI en CPI zowel volledig als praktisch zijn.

Data-only aanvallen vormen echter een unieke uitdaging: ze kunnen alle data overschrijven, wat elke operatie kan corrumperen. Hierdoor zijn mitigaties meestal óf volledig, óf praktisch, maar niet beide:

  • Volledige verdedigingen (bijv. memory safety, DFI) zijn vaak onpraktisch vanwege slechte prestaties of zware wijzigingen in software of hardware.
  • Praktische verdedigingen (bijv. memory error scanning, selectieve DFI, syscall filtering) zijn niet volledig, omdat ze — zoals Einstein aantoont — delen van het aanvalsoppervlak kwetsbaar laten.

Omdat Einstein exploits genereert die praktische verdedigingen triviaal omzeilen, benadrukken we dat leveranciers serieus moeten overwegen om één of meer volledige verdedigingsmechanismen in te zetten. Leveranciers kunnen Einstein ook gebruiken om kwetsbaarheden per geval aan te pakken, maar een algemene oplossing — door volledige verdedigingen praktischer te maken — is een dringende richting voor toekomstig onderzoek.

Referenties

[1] M. Miller, "Trends, challenges, and strategic shifts in the software vulnerability mitigation landscape," in BlueHat, 2019. [2] Chromium, "Memory safety," https://www.chromium.org/Home/chromium-security/memory-safety/. [3] D. Hosfelt, “Implications of Rewriting a Browser Component in Rust,” https://hacks.mozilla.org/2019/02/rewriting-a-browser-component-in-rust/, 2019. [4] E. H. Spafford, “The Internet Worm Program: An Analysis,” SIGCOMM CCR, vol. 19, no. 1, 1989. [5] B. Johannesmeyer, A. Slowinska, H. Bos, and C. Giuffrida, "Practical Data-Only Attack Generation," in USENIX Security, 2024. [6] S. Chen, J. Xu, E. C. Sezer, P. Gauriar, and R. K. Iyer, “Non-Control-Data Attacks Are Realistic Threats,” in USENIX Security, 2005. [7] R. Rogowski, M. Morton, F. Li, F. Monrose, K.Z. Snow, and M. Polychronakis, "Revisiting Browser Security in the Modern Era: New Data-only Attacks and Defenses," in EuroS&P, 2017. [8] J. Newsome, and D. Song, "Dynamic Taint Analysis for Automatic Detection, Analysis, and Signature Generation of Exploits on Commodity Software," in NDSS, 2005.