Hvad er SQL-injektion og hvordan forebygges i PHP-applikationer?

Så du tror, ​​at din SQL-database er effektiv og sikker mod øjeblikkelig ødelæggelse? Nå, SQL Injection er uenig!

Ja, det er øjeblikkelig ødelæggelse, vi taler om, for jeg ønsker ikke at åbne denne artikel med den sædvanlige lamme terminologi om at “skærpe sikkerheden” og “forhindre ondsindet adgang.” SQL Injection er et så gammelt trick i bogen, at alle, enhver udvikler, kender det meget godt og er godt klar over, hvordan man forhindrer det. Bortset fra det ene mærkelige tidspunkt, hvor de glider op, og resultaterne kan være intet mindre end katastrofale.

Hvis du allerede ved, hvad SQL Injection er, er du velkommen til at springe til sidste halvdel af artiklen. Men for dem, der lige er på vej inden for webudvikling og drømmer om at påtage sig flere seniorroller, er en introduktion på sin plads.

Hvad er SQL Injection?

Nøglen til at forstå SQL Injection ligger i navnet: SQL + Injection. Ordet “injektion” her har ingen medicinske konnotationer, men er snarere brugen af ​​verbet “injicer”. Sammen formidler disse to ord ideen om at sætte SQL ind i en webapplikation.

Sætte SQL ind i en webapplikation. . . hmmm. . . Er det ikke det, vi gør alligevel? Ja, men vi ønsker ikke, at en angriber skal drive vores database. Lad os forstå det ved hjælp af et eksempel.

Lad os sige, at du bygger et typisk PHP-websted til en lokal e-handelsbutik, så du beslutter dig for at tilføje en kontaktformular som denne:

<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 lad os antage, at filen send_message.php gemmer alt i en database, så butiksejerne kan læse brugerbeskeder senere. Det kan have en kode som denne:

<?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 at se, om denne bruger allerede har en ulæst besked. Forespørgslen SELECT * fra meddelelser, hvor navn = $navn virker simpel nok, ikke?

FORKERT!

I vores uskyld har vi åbnet dørene for øjeblikkelig ødelæggelse af vores database. For at dette kan ske, skal angriberen have følgende betingelser opfyldt:

  • Applikationen kører på en SQL-database (i dag er næsten alle applikationer)
  • Den aktuelle databaseforbindelse har “rediger” og “slet” tilladelser på databasen
  • Navnene på de vigtige tabeller kan gættes
  30 bedste gratis Chromecast-apps

Det tredje punkt betyder, at nu hvor angriberen ved, at du driver en e-handelsbutik, gemmer du meget sandsynligt ordredataene i en ordretabel. Bevæbnet med alt dette, alt hvad angriberen skal gøre er at angive dette som deres navn:

Joe; afkorte ordrer;? Ja Hr! Lad os se, hvad forespørgslen bliver, når den bliver udført af PHP-scriptet:

VÆLG * FRA beskeder WHERE navn = Joe; afkorte ordrer;

Okay, den første del af forespørgslen har en syntaksfejl (ingen anførselstegn omkring “Joe”), men semikolon tvinger MySQL-motoren til at begynde at fortolke en ny: afkort ordrer. Bare sådan er hele ordrehistorikken væk i et enkelt slag!

Nu hvor du ved, hvordan SQL Injection fungerer, er det tid til at se på, hvordan du stopper det. De to betingelser, der skal være opfyldt for en vellykket SQL-injektion, er:

  • PHP-scriptet skal have rettigheder til at ændre/slette i databasen. Jeg tror, ​​at dette gælder for alle applikationer, og du vil ikke være i stand til at gøre dine applikationer skrivebeskyttet. 🙂 Og gæt hvad, selvom vi fjerner alle ændringsprivilegier, kan SQL-injektion stadig tillade nogen at køre SELECT-forespørgsler og se hele databasen, inklusive følsomme data. Med andre ord virker det ikke at reducere databaseadgangsniveauet, og din applikation har brug for det alligevel.
  • Brugerinput behandles. Den eneste måde SQL-injektion kan fungere på er, når du accepterer data fra brugere. Endnu en gang er det ikke praktisk at stoppe alle input til din applikation, bare fordi du er bekymret for SQL-injektion.
  • Forebyggelse af SQL-injektion i PHP

    Nu, i betragtning af at databaseforbindelser, forespørgsler og brugerinput er en del af livet, hvordan forhindrer vi så SQL-injektion? Heldigvis er det ret simpelt, og der er to måder at gøre det på: 1) rense brugerinput og 2) bruge forberedte udsagn.

    Rengør brugerinput

    Hvis du bruger en ældre PHP-version (5.5 eller lavere, og dette sker meget på delt hosting), er det klogt at køre alle dine brugerinput gennem en funktion kaldet mysql_real_escape_string(). Grundlæggende, hvad det gør, fjerner det alle specialtegn i en streng, så de mister deres betydning, når de bruges af databasen.

    For eksempel, hvis du har en streng som Jeg er en streng, kan det enkelte anførselstegn (‘) bruges af en angriber til at manipulere databaseforespørgslen, der oprettes, og forårsage en SQL-injektion. At køre det gennem mysql_real_escape_string() producerer I’m a string, som tilføjer en omvendt skråstreg til det enkelte citat og undslipper det. Som et resultat bliver hele strengen nu sendt som en harmløs streng til databasen, i stedet for at kunne deltage i forespørgselsmanipulation.

      Sådan løses problemet med 'Antimalware Service Executable' med høj CPU-brug

    Der er én ulempe ved denne tilgang: det er en rigtig, rigtig gammel teknik, der går sammen med de ældre former for databaseadgang i PHP. Fra og med PHP 7 eksisterer denne funktion ikke engang længere, hvilket bringer os til vores næste løsning.

    Brug forberedte udsagn

    Forberedte erklæringer er en måde at gøre databaseforespørgsler mere sikkert og pålideligt på. Ideen er, at i stedet for at sende den rå forespørgsel til databasen, fortæller vi først databasen strukturen af ​​den forespørgsel, vi sender. Det er det, vi mener med at “forberede” en erklæring. Når en erklæring er udarbejdet, videregiver vi oplysningerne som parametriserede input, så databasen kan “udfylde hullerne” ved at tilslutte inputs til den forespørgselsstruktur, vi sendte før. Dette fjerner enhver speciel kraft, som inputs kan have, hvilket får dem til at blive behandlet som blot variable (eller nyttelast, om du vil) i hele processen. Sådan ser forberedte udsagn ud:

    <?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 ved, at processen lyder unødigt kompleks, hvis du er ny til forberedte udsagn, men konceptet er besværet værd. Her er en fin introduktion til det.

    Til dem, der allerede er bekendt med PHP’s PDO-udvidelse og bruge den til at lave forberedte erklæringer, har jeg et lille råd.

    Advarsel: Vær forsigtig, når du opsætter PDO

    Når vi bruger PDO til databaseadgang, kan vi blive suget ind i en falsk følelse af sikkerhed. “Åh, ja, jeg bruger PDO. Nu behøver jeg ikke tænke på andet” — sådan går vores tankegang generelt. Det er rigtigt, at PDO (eller MySQLi forberedte sætninger) er nok til at forhindre alle mulige SQL-injektionsangreb, men du skal være forsigtig, når du sætter det op. Det er almindeligt bare at kopiere og indsætte kode fra selvstudier eller fra dine tidligere projekter og gå videre, men denne indstilling kan fortryde alt:

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

    Hvad denne indstilling gør, er at bede PDO om at efterligne forberedte sætninger i stedet for rent faktisk at bruge funktionen forberedte sætninger i databasen. Følgelig sender PHP simple forespørgselsstrenge til databasen, selvom din kode ser ud som om den skaber forberedte sætninger og indstiller parametre og alt det der. Med andre ord, du er lige så sårbar over for SQL-injektion som før. 🙂

      En introduktion til Dual Track Agile for produktchefer

    Løsningen er enkel: Sørg for, at denne emulering er indstillet til falsk.

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

    Nu er PHP-scriptet tvunget til at bruge forberedte sætninger på databaseniveau, hvilket forhindrer alle former for SQL-injektion.

    Forebyggelse af brug af WAF

    Ved du, at du også kan beskytte webapplikationer mod SQL-injektion ved at bruge WAF (webapplikationsfirewall)?

    Nå, ikke bare SQL-injektion, men mange andre lag 7-sårbarheder som cross-site scripting, brudt godkendelse, cross-site forfalskning, dataeksponering osv. Enten kan du bruge self-hosted som Mod Security eller cloud-baseret som følgende.

    SQL-injektion og moderne PHP-rammer

    SQL-indsprøjtningen er så almindelig, så nem, så frustrerende og så farlig, at alle moderne PHP-webrammer er indbygget med modforanstaltninger. I WordPress, for eksempel, har vi $wpdb->prepare()-funktionen, hvorimod hvis du bruger et MVC-framework, gør det alt det beskidte arbejde for dig, og du behøver ikke engang at tænke på at forhindre SQL-injektion. Det er lidt ærgerligt, at man i WordPress skal udarbejde udsagn eksplicit, men hey, det er WordPress vi taler om. 🙂

    I hvert fald, min pointe er, at den moderne race af webudviklere ikke behøver at tænke på SQL-injektion, og som et resultat er de ikke engang klar over muligheden. Som sådan, selvom de lader en bagdør stå åben i deres applikation (måske er det en $_GET-forespørgselsparameter og gamle vaner med at affyre en beskidt forespørgsel), kan resultaterne være katastrofale. Så det er altid bedre at tage sig tid til at dykke dybere ned i fundamentet.

    Konklusion

    SQL Injection er et meget grimt angreb på en webapplikation, men det kan nemt undgås. Som vi så i denne artikel, er det kun at være forsigtig, når du behandler brugerinput (i øvrigt er SQL Injection ikke den eneste trussel, som håndtering af brugerinput medfører) og forespørgsler i databasen. Når det er sagt, arbejder vi ikke altid med sikkerheden i en webramme, så det er bedre at være opmærksom på denne type angreb og ikke falde for den.