Hva er SQL-injeksjon og hvordan forhindres i PHP-applikasjoner?

Så du tror at SQL-databasen din er effektiv og trygg mot umiddelbar ødeleggelse? Vel, SQL Injection er uenig!

Ja, det er umiddelbar ødeleggelse vi snakker om, fordi jeg ikke ønsker å åpne denne artikkelen med den vanlige lamme terminologien om «strammere sikkerheten» og «hindre ondsinnet tilgang.» SQL Injection er et så gammelt triks i boken at alle, hver utviklere, vet om det veldig godt og er godt klar over hvordan man kan forhindre det. Bortsett fra den ene merkelige gangen da de sklir opp, og resultatene kan være intet mindre enn katastrofale.

Hvis du allerede vet hva SQL Injection er, kan du gjerne hoppe til siste halvdel av artikkelen. Men for de som nettopp kommer innen webutvikling og drømmer om å ta på seg flere seniorroller, er en introduksjon på sin plass.

Hva er SQL-injeksjon?

Nøkkelen til å forstå SQL Injection ligger i navnet: SQL + Injection. Ordet «injeksjon» her har ingen medisinske konnotasjoner, men er snarere bruken av verbet «injisere.» Sammen formidler disse to ordene ideen om å sette SQL inn i en nettapplikasjon.

Sette SQL inn i en webapplikasjon. . . hmmm . . . Er det ikke det vi gjør uansett? Ja, men vi vil ikke at en angriper skal drive databasen vår. La oss forstå det ved hjelp av et eksempel.

La oss si at du bygger et typisk PHP-nettsted for en lokal e-handelsbutikk, så du bestemmer deg for å legge til et kontaktskjema som dette:

<form action="record_message.php" method="POST">
  <label>Your name</label>
  <input type="text" name="name">
  
  <label>Your message</label>
  <textarea name="message" rows="5"></textarea>
  
  <input type="submit" value="Send">
</form>

Og la oss anta at filen send_message.php lagrer alt i en database slik at butikkeierne kan lese brukermeldinger senere. Den kan ha en kode som dette:

<?php

$name = $_POST['name'];
$message = $_POST['message'];

// check if this user already has a message
mysqli_query($conn, "SELECT * from messages where name = $name");

// Other code here

Så du prøver først å se om denne brukeren allerede har en ulest melding. Spørringen SELECT * fra meldinger der navn = $navn virker enkelt nok, ikke sant?

FEIL!

I vår uskyld har vi åpnet dørene for umiddelbar ødeleggelse av databasen vår. For at dette skal skje, må angriperen ha følgende betingelser oppfylt:

  • Applikasjonen kjører på en SQL-database (i dag er nesten alle applikasjoner)
  • Den gjeldende databasetilkoblingen har «rediger» og «slett» tillatelser på databasen
  • Navnene på de viktige tabellene kan gjettes
  Slik deaktiverer du Siri-forslag på iPhone-låseskjermen

Det tredje punktet betyr at nå som angriperen vet at du driver en e-handelsbutikk, vil du sannsynligvis lagre ordredataene i en ordretabell. Bevæpnet med alt dette, alt angriperen trenger å gjøre er å oppgi dette som navnet sitt:

Joe; avkorte ordrer;? Ja, sir! La oss se hva spørringen blir når den blir utført av PHP-skriptet:

SELECT * FRA meldinger WHERE navn = Joe; avkorte bestillinger;

Ok, den første delen av spørringen har en syntaksfeil (ingen anførselstegn rundt «Joe»), men semikolonet tvinger MySQL-motoren til å begynne å tolke en ny: avkorte ordrer. Akkurat sånn er hele ordrehistorikken borte i et enkelt grep!

Nå som du vet hvordan SQL Injection fungerer, er det på tide å se på hvordan du stopper det. De to betingelsene som må oppfylles for vellykket SQL-injeksjon er:

  • PHP-skriptet bør ha rettigheter til å endre/slette i databasen. Jeg tror dette er sant for alle applikasjoner, og du kommer ikke til å kunne gjøre applikasjonene skrivebeskyttede. 🙂 Og gjett hva, selv om vi fjerner alle endringsprivilegier, kan SQL-injeksjon fortsatt tillate noen å kjøre SELECT-spørringer og se all databasen, inkludert sensitive data. Med andre ord, å redusere databasetilgangsnivået fungerer ikke, og applikasjonen din trenger det uansett.
  • Brukerinnspill er under behandling. Den eneste måten SQL-injeksjon kan fungere på er når du godtar data fra brukere. Nok en gang er det ikke praktisk å stoppe alle inndata for applikasjonen din bare fordi du er bekymret for SQL-injeksjon.
  • Forhindrer SQL-injeksjon i PHP

    Nå, gitt at databasetilkoblinger, spørringer og brukerinndata er en del av livet, hvordan forhindrer vi SQL-injeksjon? Heldigvis er det ganske enkelt, og det er to måter å gjøre det på: 1) rense brukerinnspill, og 2) bruke forberedte uttalelser.

    Rengjør brukerinnspill

    Hvis du bruker en eldre PHP-versjon (5.5 eller lavere, og dette skjer mye på delt hosting), er det lurt å kjøre alle brukerinndataene dine gjennom en funksjon kalt mysql_real_escape_string(). I utgangspunktet, hva det gjør det fjerner alle spesialtegn i en streng slik at de mister sin betydning når de brukes av databasen.

    For eksempel, hvis du har en streng som jeg er en streng, kan det enkle anførselstegnet («) brukes av en angriper for å manipulere databasespørringen som opprettes og forårsake en SQL-injeksjon. Å kjøre den gjennom mysql_real_escape_string() produserer I’m a string, som legger til en omvendt skråstrek til det enkle sitatet, og unnslipper det. Som et resultat blir hele strengen nå sendt som en ufarlig streng til databasen, i stedet for å kunne delta i spørringsmanipulering.

      6 beste proxy-administratorer for å administrere fullmakter i stor skala

    Det er en ulempe med denne tilnærmingen: det er en veldig, veldig gammel teknikk som går sammen med de eldre formene for databasetilgang i PHP. Fra og med PHP 7 eksisterer ikke denne funksjonen engang lenger, noe som bringer oss til vår neste løsning.

    Bruk utarbeidede utsagn

    Forberedte uttalelser er en måte å gjøre databasespørringer sikrere og mer pålitelig. Tanken er at i stedet for å sende den rå spørringen til databasen, forteller vi først databasen strukturen til spørringen vi skal sende. Dette er hva vi mener med å «forberede» en uttalelse. Når en setning er utarbeidet, sender vi informasjonen som parametriserte innganger slik at databasen kan «fylle hullene» ved å plugge inn inngangene til spørringsstrukturen vi sendte før. Dette tar bort all spesiell kraft inngangene kan ha, og får dem til å bli behandlet som bare variabler (eller nyttelast, om du vil) i hele prosessen. Slik ser forberedte uttalelser ut:

    <?php
    $servername = "localhost";
    $username = "username";
    $password = "password";
    $dbname = "myDB";
    
    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    
    // Check connection
    if ($conn->connect_error) {
        die("Connection failed: " . $conn->connect_error);
    }
    
    // prepare and bind
    $stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)");
    $stmt->bind_param("sss", $firstname, $lastname, $email);
    
    // set parameters and execute
    $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 "New records created successfully";
    
    $stmt->close();
    $conn->close();
    ?>

    Jeg vet at prosessen høres unødvendig kompleks ut hvis du er ny på forberedte uttalelser, men konseptet er vel verdt innsatsen. Her er en fin introduksjon til det.

    For de som allerede er kjent med PHPs PDO-utvidelse og bruke den til å lage forberedte uttalelser, har jeg et lite råd.

    Advarsel: Vær forsiktig når du setter opp PUD

    Når vi bruker PUD for databasetilgang, kan vi bli sugd inn i en falsk følelse av sikkerhet. «Ah, vel, jeg bruker PUD. Nå trenger jeg ikke tenke på noe annet» — slik går vår tenkning generelt. Det er sant at PDO (eller MySQLi preparerte setninger) er nok til å forhindre alle slags SQL-injeksjonsangrep, men du må være forsiktig når du setter den opp. Det er vanlig å bare kopiere og lime inn kode fra opplæringsprogrammer eller fra tidligere prosjekter og gå videre, men denne innstillingen kan angre alt:

    $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

    Det denne innstillingen gjør er å fortelle PDO å emulere forberedte setninger i stedet for å faktisk bruke funksjonen for forberedt setninger i databasen. Følgelig sender PHP enkle spørringsstrenger til databasen selv om koden din ser ut som den lager forberedte setninger og stiller inn parametere og alt det der. Med andre ord, du er like sårbar for SQL-injeksjon som før. 🙂

      Docker vs Virtual Machine (VM) – Forstå forskjellene

    Løsningen er enkel: sørg for at denne emuleringen er satt til falsk.

    $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

    Nå er PHP-skriptet tvunget til å bruke forberedte setninger på databasenivå, og forhindrer alle slags SQL-injeksjoner.

    Forhindrer bruk av WAF

    Vet du at du også kan beskytte webapplikasjoner mot SQL-injeksjon ved å bruke WAF (webapplikasjonsbrannmur)?

    Vel, ikke bare SQL-injeksjon, men mange andre lag 7-sårbarheter som skripting på tvers av nettsteder, ødelagt autentisering, forfalskning på tvers av nettsteder, dataeksponering, etc. Enten kan du bruke selvvert som Mod Security eller skybasert som følgende.

    SQL-injeksjon og moderne PHP-rammeverk

    SQL-injeksjonen er så vanlig, så enkel, så frustrerende og så farlig at alle moderne PHP-nettrammeverk kommer innebygd med mottiltak. I WordPress, for eksempel, har vi funksjonen $wpdb->prepare(), mens hvis du bruker et MVC-rammeverk, gjør det alt det skitne arbeidet for deg, og du trenger ikke engang å tenke på å forhindre SQL-injeksjon. Det er litt irriterende at man i WordPress må utarbeide uttalelser eksplisitt, men hei, det er WordPress vi snakker om. 🙂

    Uansett, poenget mitt er at den moderne rasen av webutviklere ikke trenger å tenke på SQL-injeksjon, og som et resultat er de ikke engang klar over muligheten. Som sådan, selv om de lar én bakdør stå åpen i applikasjonen deres (kanskje det er en $_GET søkeparameter og gamle vaner med å skyte av et skittent spørringsspark), kan resultatene være katastrofale. Så det er alltid bedre å ta seg tid til å dykke dypere ned i fundamentene.

    Konklusjon

    SQL Injection er et veldig ekkelt angrep på en nettapplikasjon, men det er lett å unngå. Som vi så i denne artikkelen, å være forsiktig når du behandler brukerinndata (forresten, SQL Injection er ikke den eneste trusselen som håndtering av brukerinndata bringer) og spørring i databasen er alt som skal til. Når det er sagt, jobber vi ikke alltid med sikkerheten til et nettrammeverk, så det er bedre å være klar over denne typen angrep og ikke falle for den.