Tror du kanskje at SQL-databasen din er fullstendig sikker og immun mot plutselig ødeleggelse? Da må du tro om igjen, for SQL-injeksjon er en reell trussel!
Ja, det er snakk om potensielt umiddelbar katastrofe. Jeg ønsker ikke å starte denne artikkelen med de vanlige, kjedelige frasene om «å stramme sikkerheten» og «forhindre ondsinnede angrep». SQL-injeksjon er en så gammel metode at alle utviklere burde være godt kjent med det og vite hvordan det kan unngås. Likevel kan det skje at man glemmer seg, og konsekvensene kan være svært alvorlige.
Hvis du allerede vet hva SQL-injeksjon er, kan du gjerne hoppe over til den andre delen av artikkelen. Men for de som er nye innen webutvikling og ser frem til mer avanserte roller, er en introduksjon nødvendig.
Hva er SQL-injeksjon?
Nøkkelen til å forstå SQL-injeksjon ligger i selve navnet: SQL + injeksjon. Ordet «injeksjon» refererer ikke til medisinske forhold, men derimot til handlingen å «injisere» noe. Disse to ordene kombinert beskriver ideen om å sette inn SQL-kode i en webapplikasjon.
Sette inn SQL-kode i en webapplikasjon? Er ikke det vi gjør uansett? Jo, men vi ønsker ikke at en angriper skal ha kontroll over databasen vår. La oss forklare dette ved hjelp av et eksempel.
Tenk deg at du utvikler en enkel PHP-basert nettside for en lokal nettbutikk, og du bestemmer deg for å implementere et kontaktskjema:
Anta at filen «send_message.php» lagrer alle henvendelser i en database slik at butikkeierne kan lese dem senere. Koden kan se omtrent slik ut:
<?php $name = $_POST['name']; $message = $_POST['message']; // Sjekk om brukeren har sendt melding tidligere mysqli_query($conn, "SELECT * from meldinger where navn = $name"); // Annen kode her
Du forsøker først å sjekke om brukeren allerede har en ulest melding. SELECT * fra meldinger der navn = $navn virker greit nok, ikke sant?
FEIL!
I vår uvitenhet har vi åpnet døren for en potensiell ødeleggelse av databasen. For at dette skal skje, må følgende betingelser være oppfylt:
- Applikasjonen bruker en SQL-database (noe de fleste applikasjoner gjør i dag).
- Den gjeldende databasetilkoblingen har tillatelse til å redigere og slette data i databasen.
- Navnene på de viktige tabellene kan gjettes.
Det tredje punktet betyr at hvis angriperen vet at du driver en nettbutikk, vil du sannsynligvis lagre ordredata i en tabell som heter «ordre». Med denne informasjonen, kan angriperen oppgi følgende som sitt navn:
Joe; avkort ordre;? Ja takk!
La oss se hvordan spørringen blir når den utføres av PHP-skriptet:
SELECT * FRA meldinger WHERE navn = Joe; avkort ordre;
Ok, den første delen av spørringen har en syntaksfeil (det mangler anførselstegn rundt «Joe»), men semikolonet tvinger MySQL-motoren til å tolke en ny kommando: «avkort ordre». Dermed er hele ordrehistorikken borte på et øyeblikk!
Nå som du har forstått hvordan SQL-injeksjon fungerer, er det på tide å se hvordan vi kan stoppe det. De to betingelsene som må være til stede for en vellykket SQL-injeksjon er:
- PHP-skriptet har tillatelse til å endre/slette data i databasen. Dette gjelder som regel for alle applikasjoner, og det er ikke praktisk å gjøre dem skrivebeskyttet. Selv om vi fjerner alle redigeringsrettigheter, kan SQL-injeksjon fortsatt gi angripere mulighet til å kjøre SELECT-spørringer og få tilgang til alle data, inkludert sensitiv informasjon. Derfor fungerer det ikke å begrense databasetilgangen, og applikasjonen trenger tilgang uansett.
- Brukerinput blir behandlet. SQL-injeksjon fungerer bare når du mottar data fra brukere. Det er heller ikke praktisk å fjerne alle inndata bare på grunn av bekymringer rundt SQL-injeksjon.
Hvordan Forhindre SQL-Injeksjon i PHP
Siden databasekoblinger, spørringer og brukerinput er en del av webutvikling, hvordan kan vi da forhindre SQL-injeksjon? Heldigvis er det ganske enkelt, og det finnes to måter å gjøre det på: 1) rense brukerinput og 2) bruke forberedte setninger.
Rens Brukerinput
Hvis du bruker en eldre PHP-versjon (5.5 eller lavere, noe som ikke er uvanlig ved delt hosting), kan det være fornuftig å kjøre all brukerinput gjennom en funksjon som heter «mysql_real_escape_string()». Denne funksjonen fjerner alle spesialtegn fra en tekststreng, slik at de mister sin spesielle betydning når de brukes av databasen.
Hvis du for eksempel har en tekststreng som «Jeg er en «streng’», kan enkelt anførselstegn (») misbrukes av en angriper for å manipulere databasespørringen og forårsake en SQL-injeksjon. Å kjøre denne strengen gjennom «mysql_real_escape_string()» vil resultere i «Jeg er en \’streng\’», der en omvendt skråstrek er lagt til for å «escape» anførselstegnet. Som et resultat, blir hele strengen behandlet som en ufarlig tekststreng i databasen og kan ikke lenger manipulere spørringen.
Det er en ulempe med denne tilnærmingen: det er en veldig gammel teknikk som er knyttet til eldre måter for databasetilgang i PHP. Fra og med PHP 7 eksisterer ikke denne funksjonen lenger, noe som fører oss til neste løsning.
Bruk Forberedte Setninger
Forberedte setninger er en måte å gjøre databasespørringer sikrere og mer pålitelige. I stedet for å sende den rå spørringen direkte til databasen, forteller vi først databasen om strukturen til spørringen vi skal sende. Dette er hva vi mener med å «forberede» en setning. Når en setning er forberedt, sender vi informasjonen som parameteriserte inndata. Databasen vil da «fylle ut hullene» ved å plugge inn inndataene i spørringsstrukturen vi sendte tidligere. Dette fjerner alle spesialkrefter inndataene måtte ha, og får dem til å bli behandlet som variabler under hele prosessen. Slik ser forberedte setninger ut:
<?php $servername = "localhost"; $username = "brukernavn"; $password = "passord"; $dbname = "minDB"; // Opprett tilkobling $conn = new mysqli($servername, $username, $password, $dbname); // Sjekk tilkobling if ($conn->connect_error) { die("Tilkoblingen mislyktes: " . $conn->connect_error); } // Forbered og bind $stmt = $conn->prepare("INSERT INTO MinGjester (fornavn, etternavn, epost) VALUES (?, ?, ?)"); $stmt->bind_param("sss", $firstname, $lastname, $email); // Sett parametere og utfør $firstname = "John"; $lastname = "Doe"; $email = "[email protected]"; $stmt->execute(); $firstname = "Mary"; $lastname = "Moe"; $email = "[email protected]"; $stmt->execute(); $firstname = "Julie"; $lastname = "Dooley"; $email = "[email protected]"; $stmt->execute(); echo "Nye oppføringer lagret."; $stmt->close(); $conn->close(); ?>
Jeg vet at prosessen kan virke unødvendig kompleks hvis du er ny til forberedte setninger, men konseptet er verdt innsatsen. Her er en fin introduksjon til det.
For de som allerede er kjent med PHPs PDO-utvidelse og bruker den til å lage forberedte setninger, har jeg et lite råd.
Advarsel: Vær forsiktig ved oppsett av PDO
Når vi bruker PDO for databasetilgang, kan vi få en falsk følelse av sikkerhet. «Jeg bruker PDO, så nå trenger jeg ikke å tenke på noe annet» – tenker vi ofte. Det er sant at PDO (eller MySQLi forberedte setninger) er nok til å forhindre alle typer SQL-injeksjonsangrep, men du må være forsiktig når du setter den opp. Det er vanlig å bare kopiere og lime inn kode fra veiledninger eller tidligere prosjekter og gå videre, men denne innstillingen kan ødelegge alt:
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
Denne innstillingen forteller PDO å emulere forberedte setninger i stedet for å bruke den faktiske forberedte setningsfunksjonen i databasen. Derfor sender PHP vanlige spørringsstrenger til databasen selv om koden din ser ut som om den lager forberedte setninger og setter parametere osv. Du er dermed like sårbar for SQL-injeksjon som før. 🙂
Løsningen er enkel: sørg for at denne emuleringen er satt til «false».
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
Nå er PHP-skriptet tvunget til å bruke forberedte setninger på databasenivå, og forhindrer dermed SQL-injeksjoner.
Forhindre SQL-Injeksjon Med WAF
Visste du at du også kan beskytte dine webapplikasjoner mot SQL-injeksjon ved hjelp av en WAF (Web Application Firewall)?
WAF beskytter ikke bare mot SQL-injeksjon, men også mot mange andre sårbarheter på lag 7, som kryss-side scripting, ødelagt autentisering, forfalskning på tvers av nettsteder og dataeksponering osv. Du kan bruke en selvvert løsning som Mod Security eller en skybasert løsning.
SQL-Injeksjon og Moderne PHP Rammeverk
SQL-injeksjon er så vanlig, så enkel, så frustrerende og så farlig at alle moderne PHP-webrammeverk har innebygde sikkerhetstiltak. I WordPress, for eksempel, har vi funksjonen «$wpdb->prepare()», mens et MVC-rammeverk gjør alt arbeidet for deg, slik at du ikke engang trenger å tenke på å forhindre SQL-injeksjon. Det er litt irriterende at man i WordPress må utarbeide setninger eksplisitt, men det er WordPress vi snakker om. 🙂
Poenget er at moderne webutviklere ikke nødvendigvis trenger å tenke på SQL-injeksjon, og som et resultat er de kanskje ikke klar over faren. Selv om man glemmer en bakdør i applikasjonen sin (kanskje det er en $_GET-parameter for søk, og man faller tilbake i gamle vaner), kan resultatene være katastrofale. Det er derfor alltid lurt å ta seg tid til å gå dypere inn i det grunnleggende.
Konklusjon
SQL-injeksjon er et svært ubehagelig angrep på en webapplikasjon, men det er enkelt å unngå. Som vi har sett i denne artikkelen, kreves det bare å være forsiktig når man behandler brukerinput (SQL-injeksjon er ikke den eneste trusselen ved behandling av brukerinput) og spørringer i databasen. Vi jobber ikke alltid med sikkerheten i et webrammeverk, så det er viktig å være oppmerksom på denne typen angrep for å unngå å bli et offer.