Litt mer realistiske SQL-injeksjon sårbarheter

SQL-injeksjoner finnes fortsatt i 2024. De blir litt bedre gjemt av rammeverk og mellomvare som stopper feilmeldinger fra å nå klienten. Les litt om hvordan enkelte SQL angrep treffer selv om utviklere har tatt grep for å hindre sårbarhetene.

Krister Kvaavik

Publisert:

27. mai 2024

SQL sprøyte

En liten innledning

Vi er i 2024, og siste utgave fra OWASP topp ti (fra 2021) sier at injeksjonssårbarheter endelig er vippet av tronen, dog ikke veldig langt ned. OWASP står for Open Worldwide Application Security Project og omhandler i korte trekk prosessen med å best sikre en (web) applikasjon, også ofte kalt nettsted, mot innbrudd og informasjonslekkasje. 

Disse publiserer også med ujevne mellomrom topp 10 lister over de vanligste sårbarhetene i (web) applikasjoner, samt også lister over vanligste sårbarheter i mobilapplikasjoner og API’er. I 2017 var såkalte injeksjonssårbarheter på topp. De ble i 2021 utgaven av listen dyttet ned til en tredjeplass.

Det er her du nå gjerne forstår at det kommer mer fagterminologi og forkortelser nedover og jeg kan vel også avsløre at følelsen din, om at dette blir en relativt teknisk bloggpost/artikkel, er riktig.

Dersom du er utvikler eller penetrasjonstester er du kommet til rett plass. Hvis du er prosjektleder, rådgiver eller produkteier er det godt mulig du får noe ut av det også. Det er også helt greit om du stopper her og heller kopierer lenken til de du vet kan ha nytte, eller glede, av å lese en litt "Next Level" artikkel om SQL-injeksjoner.

Som utvikler, Security Champion eller bare over snittet interessert i teknisk informasjonssikkerhet, puster du kanskje nå lettet ut over at din leder eller kollega ikke nettopp har lurt deg (med den lenken på Slack eller Teams) inn i nok en "stating the obvious" artikkel om SQL-injeksjoner og hvordan de kan utnyttes med ‘ OR 1=1. Så kjenner du kanskje også litt på irritasjonen over at artikkelen med det som er interessant for deg enda ikke har begynt. Legitimt synes jeg, la oss fortsette med SQL-injeksjoner.

 

SQL sprøyter ødelagt på bakken foran cybersikkerhet skjold

SQL injeksjoner, hvor er de?

Bruker ikke alle ORM i dag da, for å fasilitere kommunikasjon med databaser? Ikke nok med det, alle som programmerer i C# bruker vel LINQ? I tillegg til dette så vet jo alle at man i det minste skal bruke "parameteriserte" spørringer mot databasen og i det aller minste "escape" alle enkle anførselstegn? Det er jo derfor omtrent alle SQL-injeksjonssårbarheter er ryddet vekk… er det lett å tenke.

Dessverre er det ikke slik, ikke alle bruker en Object Relational Mapping (ORM), enten på grunn av latskap, mangel på kunnskap eller rett og slett noen helt syke JOINS eller andre komplekse spørringer som enten ikke er mulig med tilgjengelig ORM, eller bare veldig lite "performant". Heller ikke alle utvikler i C# og kan bruke LINQ til å redde seg. Uansett, SQL-injeksjoner er fortsatt til stede i applikasjoner i 2024, så hvor er de? Hvorfor hører vi ikke (så mye) om de, hvorfor ser vi de ikke overalt, og hvorfor blir de ikke oppdaget i penetrasjonstester?

Det er fordi de er bedre gjemt nå enn før. De fleste rammeverk er blitt gode på å ikke spy ut detaljerte feilmeldinger når avføring treffer viften, eller serveren opplever en "500 Internal Server Error" som det også kalles. Mange SQL-injeksjonangrep treffer altså fortsatt, men uten at man får noen tydelige hint på at inngangspunktet er sårbart. Applikasjonen kneler i det stille, eller enda bedre, kneler med en 400 i retur til deg med beskyldninger om at du, klienten, har gjort noe galt. Du er dermed effektivt sett blind. Dermed slipper dette gjennom fingrene på sårbarhetsskannere, men også på uerfarne penetrasjonstestere. En erfaren penetrasjonstester vet nøyaktig hva jeg nå skal skrive om, men også de kan få seg en aha-opplevelse om de bare holder ut gjennom denne neste blinde boolean/time-based SQL-injeksjonsdelen.

 

Innholdsbasert (content-based/boolean-based) og tidsbasert (time-based) SQL injeksjon

Altså, når vi ikke kan få bekreftet gjennom de enkleste angrepene at det eksisterer en SQL-injeksjon (les mer om forskjellige angrep her), kan man bruke et par andre teknikker for å avdekke disse sårbarhetene. Det som en penetrasjonstester sannsynligvis helst ønsker seg av de to teknikkene, er den såkalte innholdsbaserte (content-based eller boolean-based) teknikken. Helt enkelt fordi det angrepet kan utføres raskere. I prinsippet betyr dette at man påvirker spørringen som har sårbarheten til å returnere dataene som tiltenkt dersom et logisk/boolsk utrykk (spesifisert av angriper) validerer til sant og at ingen data (evt andre data) returneres dersom det valideres til usant.

Alle kode-eksempler tar utgangspunkt i at SQL server er av typen Microsoft SQL. Prinsippene fungerer også med andre SQL-språk som eksempelvis MySQL/MariaDB og Postgres, men syntax må tilpasses til hvert enkelt.

Vi kan se for oss at følgende spørring (MSSQL) er sårbar for SQL-injeksjon i ORDER BY klausulen, nærmere bestemt "ORDER BY  ‘[injeksjon her]’":

"SELECT shoesize, bloodtype, favourite_drink FROM developer_registry WHERE security_champion = " + sanitizeSQLValue([GET parameter issecchamp]) + " ORDER BY ' " + [GET parameter orderby] + " ' ";

Vi kan da styre data som blir returnert ved å sette inn et logisk utrykk her, et eksempel er:

' (CASE WHEN 123=123 THEN 'shoesize' ELSE 'bloodtype' END)--

I kjent stil begynner angrepskoden med et enkelt anførselstegn for å bryte ut av tekststrengen vi er forventet å levere. Deretter kommer et logisk utrykk som blir evaluert av SQL serveren. Det avsluttes også som kjent med å kommentere ut resten av spørringen.

SELECT shoesize, bloodtype, favourite_drink FROM developer_registry WHERE security_champion = 1 ORDER BY ''(CASE WHEN 123=123 THEN 'shoesize' ELSE 'bloodtype' END)-- ';

Siden dette eksempelet inneholder en tautologi (123 vil alltid være lik 123), kan vi forvente å få data returnert fra spørringen, sortert etter skostørrelse. Dersom vi endrer CASE til å være (CASE WHEN123=124 THEN 'shoesize' ELSE 'bloodtype' END) vil vi få listen over alle utviklere som er Security Champions sortert etter blodtype. Med forbehold om at det finnes utviklere med ulike skostørrelser og blodtyper som også er Security Champions i databasen vil vi med denne teknikken tvinge frem to forskjellige resultater.

Både trusselaktører og penetrasjonstestere ønsker helst å få responser på forskjellige størrelser slik at det er enkelt å avgjøre utfallet av angrepet. Tatt dette i betraktning, er det litt suboptimalt at sårbarheten er i ORDER BY klausulen. Da får vi den samme dataen tilbake hver gang, men rekkefølgen på bytes er annerledes. Null stress tenker den erfarne penetrasjonstesteren, vi kan bare checksum’e responsen for å kunne avgjøre hva som er TRUE og hva som er FALSE av de logiske utrykkene vi bruker i angrepene våre.

Generelt sett kan man i noen tilfeller også gjøre ELSE delen slik at spørringen feiler og dermed ikke returnerer noen liste i det hele tatt. Da får vi en forskjell i størrelsen på responsen.

Et hakket mer interessant eksempel er å hente ut passord hasher (som vi deretter kan knekke) som for eksempel kan gjøres slik:

SQL spørring som henter ut passord hasher

Angrepskode er limt inn som bilder fordi publiseringsløsningen sin WAF blir helt vill flere ganger gjennom denne artikkelen når man prøver å lagre. Den gjør jo bare jobben sin ved å stoppe data som tolkes som angrep. Det er jo unektlig litt slitsomt for penetrasjonstestere å ikke kunne kopiere og lime inn i "payloads" for testing. Så hvorfor ikke bare blidgjøre publiseringsløsningen her, så blir det en liten bonus til temaet i artikkelen og kopiering blir mulig:

SELECT shoesize, bloodtype, favourite_drink FROM developer_registry WHERE security_champion = 1 ORDER/**/BY/**/''(/**/SELECT CASE WHEN ASCII/**/(SUBSTRING/**/(/**/SELECT password FROM developer_registry WHERE userid=1),1,1))=97 THEN 'shoesize' ELSE 'bloodtype' END)--';

Tegnene /**/ blir tolket av SQL databasemotoren som en kommentar uten innhold og blir effektivt et mellomrom. Her tjener det vårt formål med å bryte mønstre som publiseringsløsningen definerer som farlige. Dette illustrerer også den falske tryggheten en Web Application Firewall (WAF) uten tilstrekkelig konfigurasjon kan gi. For dem som bruker CloudFlare, kan vi anbefale å ta i bruk WAF Attack Score for å finjustere WAF'en.

Her returneres en liste med Security Champions sortert etter skostørrelse dersom, og kun dersom, første karakteren i passordet til utvikler med userid=1 er en 'a' (97 er desimalverdien til liten bokstav 'a' i ASCII tabellen over printbare karakterer). Når vi, gjennom deduksjon, har funnet ut hva første karakteren i passordhashen til bruker nummer 1 er, går vi videre til karakter nummer to. Slik fortsetter vi til vi har hele verdien fra passordhashfeltet i databasen. Dersom systemet vi har fått tilgang til lagrer passordene i klartekst i databasen (noe som er veldig veldig fy-fy), så får vi jo tak i passordet direkte og slipper den gøye delen med passord-knekking.

Her vil en utvikler, iallefall en security champion med fokus på injeksjoner, raskt se problemstillingen og oppdatere koden i ORDER BY klausulen til å være følgende:

ORDER BY ' + sanitizeSQLValue( [GET parameter orderby] ) +';

 

Noen skriver sanitizeSQLValue() slik:

Sanitize SQL Value function eksempel hvor alle enkle anførselstegn erstattes med 2 enkle anførselstegn

Andre skriver den slik:

Sanitize SQL Value function eksempel hvor tom streng returneres hvis enkelt anførselstegn funnet

Mange skriver helt andre løsninger og mange holder seg heldigvis til "beste praksiser" hva gjelder sanitisering. Dette er bare eksempler på kode jeg har sett gjentatte ganger.

Uansett er vel hele spørringen trygg?

SQL spørring

Ja, vil sannsynligvis de fleste statiske kodeanalyseverktøyene si. Et trent øye vil derimot se at det fortsatt er en SQL-injeksjon-sårbarhet, men i WHERE klausulen. sanitizeSQLValue-funksjonen beskytter riktignok mot en angriper som forsøker å "escape" seg ut av streng-delen av spørringen, men ikke dersom selve strengverdien ikke er omsluttet av enkle anførselstegn. Mange ganger ønsker man å ta imot en tallverdi (der enkle anførselstegn ikke er nødvendig), men glemmer å spesifisere/tvinge denne verdien til et heltall før den sendes til databasen.

Dette betyr at penetrasjonstestere, til og med erfarne, godt mulig har oversett flere SQL-injeksjon sårbarheter dersom de har gjort antagelsen om at det er nødvendig med enkle anførselstegn for å bryte ut av en strengverdi.

 

En annen utfordring når enkle anførselstegn blir terminert eller tryllet bort, er at man ikke helt trivielt, i MSSQL verden, kan bruke den klassiske "vent 5 sekunder" sjekken for å oppdage en blind SQL-injeksjon:

WAITFOR DELAY ’00:00:05’ 

Kort fortalt er dette en teknikk for å oppdage "skjulte" SQL-injeksjon sårbarheter. Det fungerer slik at man ber databasemotoren om å vente litt før den svarer. På den måten kan vi «ta tiden» på angrepet og dersom det tar omtrent 5 sekunder så vet vi at vi har fått en fot innenfor.

I MSSQL krever, ifølge dokumentasjonen, denne teknikken en streng med tid formatert slik hh:mm[[:ss].mss] eller en variabel.

I de fleste tilfeller kan vi bruke "Character code" istedet for en tradisjonell streng. Da kan det klassiske MSSQL-angrepet med strengen "00:00:03" (3 sekunder fordi det er litt voldsomt å kaste vekk 5 sekunder av livet på dette) oversettes til noe lignende:

SQL spørring med streng i variabel

Her deklareres en variabel sqlqwerty som effektivt settes til strengen: "00:00:03". Selv om dette er skrevet over flere linjer i eksempelet over er det fortsatt bare en spørring og setter ingen krav til at systemet støtter "stacked queries". Alternativt kan man jo såklart kjøre aritmetisk tunge spørringer for å oppdage blinde SQL-injeksjon sårbarheter.

 

Litt annerledes testing

Når dette er sagt, må vi ikke glemme at vi kan oppdage evaluering av logiske utrykk enten på applikasjonsnivå eller på databasenivå, ved å sette inn så enkle utrykk som 1339-1337 hvor det er forventet tallverdier og for eksempel

Konkatinering av karakterkoder i SQL språket

CONCAT/**/(CHAR/**/(116)+CHAR/**/(101),CHAR/**/(115)+CHAR/**/(116))

eller bare:

CHAR/**/(116)+CHAR/**/(101)+CHAR/**/(115)+CHAR/**/(116)

der det er forventet en strengverdi, angitt som "test" i dette eksempelet (igjen er kopier/lim inn varianten skrevet på denne måten for å blidgjøre publiseringsløsningen).

Altså dersom applikasjonen responderer unikt på en inndataverdi som er enten 2, eller "test" og den samme responsen kan oppnås med de logiske utrykkene istedet, har man sannsynligvis funnet en injeksjonssårbarhet.

Hvis vi ser tilbake på angrepet med å hente ut passord hasher, kan vi raskt illustrere hvordan en trusselaktør ville gjort dette uten å bruke hverken komma eller enkle anførselstegn:

Eksempel SQL angrep for å hente ut passordhasher
Uff, mangler en sluttparantes her...

Karakter 116 er liten 't', karakter 37 er '%'. Denne spørringen sier omtrent følgende: Hent skostørrelse, blodtype og favoritt drikke fra alle utviklere der utvikleren (hvis passordet til utvikler med brukerid 1 er t%), er en security champion.

Prosent-tegnet er et såkalt wildcard, og password = t% leses som; passordet begynner på t og slutter på hva som helst. Hvis vi får et resultat der utviklerene er security champions vet vi at vi har truffet på karakteren. På denne måten tar vi en bokstav (i dette tilfellet) om gangen og inkrementerer oss gradvis gjennom passord strengen.

SELECT shoesize, bloodtype, favourite_drink FROM developer_registry WHERE security_champion = (/**/SELECT CASE WHEN (/**/SELECT password FROM developer_registry WHERE userid=1 AND password LIKE CHAR/**/(116)+CHAR/**/(37) THEN 1 ELSE 0 END))-- ORDER/**/BY/**/'whatever,don’tmatter';

Så hvis du er en utvikler kan det være lurt å ta en sjekk i prosjektet du jobber på. Kanskje du nå kjenner deg moden for å bli en security champion og samtidig ta litt eierskap til akkurat injeksjoner i ditt prosjekt? Ja, det vil jeg tenker du, men hvor skal man begynne?

Et naturlig sted å begynne i en kodebase er å kartlegge alle steder hvor det gjøres spørringer mot databasen. Man kan eksempelvis begynne med CTRL-F (eller på annen måte søk gjennom alle filer) etter følgende strenger (husk case insensitive):

 

  • "select "
  • "insert "
  • "delete "
  • "update "
  • [space]where[space]
  • [space]like[space]
  • [space]order by[space]
  • RegEx: "[A-Za-z.\s()]+\s*=\s*" (treffer eksempelvis: "VALUE = ")
  • query = / query=
  • sql = / sql= 

Denne listen er på ingen måte uttømmende og kun ment som en start. Man ser ofte raskt konvensjon som er brukt for å navngi variabler som inneholder SQL spørringer og kan da bruke dette som søkestreng. Videre kan man bruke Regular Expressions til å dekke flere varianter.

Hvis du er en penetrasjonstester og har lyst å bruke dette videre, husker du såklart på at det meste her er psudokode og tilpasninger blir nødvendig, både for spørrespråksmak (det var et gøyt ord det) og at ting ofte må enkodes på veien da eksempelvis '+' tegnet er mellomrom i HTML-verden.

Hvis du er en prosjektleder eller produkteier og du leste artikkelen til endes, til tross for forsøket på å skremme deg vekk, forstår du kanskje at vi i Bouvet har litt over gjennomsnittet greie på teknisk sikkerhet, sikker koding, security champions og penetrasjonstesting til tross for at vi kanskje ikke roper høyest om det 😉. Finn oss på [email protected]