Vad är SQL Injection och hur man förhindrar i PHP-applikationer?

By rik

Skydda Din SQL-Databas Från SQL-Injektionsattacker

Tror du att din SQL-databas är helt säker och immun mot plötslig förstörelse? Tänk om! SQL-injektion kan snabbt leda till allvarliga problem.

Vi pratar inte bara om att ”förbättra säkerheten” eller ”förebygga obehörig åtkomst”, utan om risken för direkt skada. SQL-injektion är en välkänd teknik som de flesta utvecklare känner till och vet hur man undviker. Men ibland händer det ändå, och resultaten kan vara förödande.

Om du redan är bekant med SQL-injektion, kan du hoppa fram till den senare delen av artikeln. Men för nykomlingar inom webbutveckling som siktar på mer avancerade roller, är en grundläggande introduktion nödvändig.

Vad Är SQL-Injektion?

Namnet SQL-injektion ger en bra indikation på vad det handlar om: SQL + injektion. Injektionen handlar inte om medicin, utan om att ”injicera” SQL-kod i en webbapplikation.

Men visst, vi använder ju SQL i våra webbapplikationer? Ja, men det ska inte vara en angripare som styr vår databas. Låt oss illustrera med ett exempel.

Anta att du bygger en webbplats med PHP för en lokal butik och lägger till ett kontaktformulär:

<form action="record_message.php" method="POST">
  <label>Ditt namn</label>
  <input type="text" name="name">

  <label>Ditt meddelande</label>
  <textarea name="message" rows="5"></textarea>

  <input type="submit" value="Skicka">
</form>

Filen send_message.php lagrar information i en databas så att butiksägarna kan läsa användarnas meddelanden senare. Koden kan se ut ungefär så här:

<?php

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

// Kontrollera om användaren redan har ett meddelande
mysqli_query($conn, "SELECT * from messages where name = $name");

// Annan kod här

Först kontrollerar du om användaren redan har ett oläst meddelande. Frågan ”SELECT * from messages where name = $name” verkar enkel, eller hur?

FEL!

Omedvetet har vi öppnat dörren för potentiell förstörelse av databasen. För att detta ska hända krävs att följande villkor uppfylls:

  • Applikationen använder en SQL-databas (vilket de flesta gör idag)
  • Databasanslutningen har behörighet att ”ändra” och ”radera” data
  • En angripare kan gissa namnen på viktiga tabeller

Den sista punkten innebär att om en angripare vet att du driver en e-handelsbutik, är det troligt att du lagrar orderinformation i en ”ordertabell”. Med den informationen kan angriparen ange följande som sitt namn:

Joe; TRUNCATE order;? Yes sir!

Låt oss se hur frågan ser ut när den körs av PHP-skriptet:

SELECT * FROM meddelanden WHERE name = Joe; TRUNCATE order;

Den första delen av frågan innehåller ett syntaxfel (inga citattecken runt ”Joe”), men semikolonet tvingar MySQL-motorn att börja tolka en ny fråga: TRUNCATE order. På en sekund är hela orderhistoriken raderad!

Nu när du förstår hur SQL-injektion fungerar, låt oss se hur du kan stoppa den. De två villkoren som måste uppfyllas för att SQL-injektion ska fungera är:

  • PHP-skriptet måste ha behörighet att ändra/radera i databasen. Detta är sant för de flesta applikationer, och du vill troligen inte göra dina applikationer skrivskyddade. Även om vi tar bort alla modifieringsprivilegier, kan SQL-injektion fortfarande tillåta någon att köra SELECT-frågor och se all data, inklusive känslig information. Att minska databasåtkomstnivån är alltså inte en effektiv lösning och din applikation behöver ändå den åtkomsten.
  • Användarinmatning bearbetas. SQL-injektion kan bara ske om du accepterar data från användare. Det är inte praktiskt att stoppa all inmatning bara för att undvika SQL-injektion.

Hur Man Förhindrar SQL-Injektion i PHP

Eftersom databasanslutningar, frågor och användarinmatning är viktiga, hur kan vi då förhindra SQL-injektion? Det finns två huvudsakliga metoder: 1) rensa användarinmatning och 2) använda förberedda uttalanden.

Rensa Användarinmatning

Om du använder en äldre PHP-version (5.5 eller lägre), är det en bra idé att köra all användarinmatning genom funktionen `mysql_real_escape_string()`. Denna funktion tar bort alla specialtecken i en sträng, så att de inte längre kan manipulera databasfrågor.

Till exempel, om du har en sträng som ”Jag är en sträng”, kan det enkla citattecknet (’) användas för att ändra databasfrågan och orsaka en SQL-injektion. När den skickas genom `mysql_real_escape_string()` blir den ”Jag\’ är en sträng”, vilket lägger till ett snedstreck framför citattecknet. Därmed skickas hela strängen oförändrad till databasen istället för att manipulera databasfrågan.

Detta tillvägagångssätt har en nackdel: det är en gammal teknik som är kopplad till äldre sätt att komma åt databaser i PHP. Från PHP 7 är den här funktionen inte längre tillgänglig, vilket leder oss till nästa lösning.

Använda Förberedda Uttalanden

Förberedda uttalanden gör databasfrågor säkrare och mer tillförlitliga. Istället för att skicka en rå fråga till databasen, informerar vi databasen om strukturen på den fråga vi kommer att skicka. Detta är vad vi menar med att ”förbereda” ett uttalande. När ett uttalande har förberetts, skickar vi informationen som parametriserad indata, så att databasen kan ”fylla i luckorna” genom att koppla in indata i frågestrukturen. Detta tar bort all specialkraft som indata kan ha. Förberedda uttalanden hanterar indata som vanliga variabler under hela processen. Här är ett exempel på förberedda uttalanden:

<?php
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDB";

// Skapa anslutning
$conn = new mysqli($servername, $username, $password, $dbname);

// Kontrollera anslutning
if ($conn->connect_error) {
    die("Anslutningen misslyckades: " . $conn->connect_error);
}

// Förbered och bind
$stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $firstname, $lastname, $email);

// Ange parametrar och kö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 "Nya poster skapade framgångsrikt";

$stmt->close();
$conn->close();
?>

Processen kan verka komplex om du är nybörjare, men det är värt att förstå konceptet. Här finns en bra introduktion.

För de som är bekanta med PHP:s PDO-tillägg och använder det för att skapa förberedda uttalanden, har jag ett litet råd.

Varning: Var Försiktig Med Inställningarna För PDO

När vi använder PDO för databasåtkomst kan vi få en falsk känsla av trygghet. ”Jag använder ju PDO, så jag behöver inte tänka på något annat”. Det stämmer att PDO (eller MySQLi preparerade uttalanden) är tillräckligt för att förhindra alla typer av SQL-injektionsattacker, men du måste vara försiktig med inställningarna. Det är vanligt att kopiera och klistra in kod från handledningar eller tidigare projekt, men den här inställningen kan göra allt ogjort:

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

Denna inställning instruerar PDO att emulera förberedda uttalanden istället för att faktiskt använda den inbyggda funktionen för förberedda uttalanden i databasen. Följaktligen skickar PHP vanliga frågesträngar till databasen, även om din kod ser ut som att den skapar förberedda uttalanden. Du är fortfarande lika sårbar för SQL-injektion. 🙂

Lösningen är enkel: Se till att emuleringen är inställd på `false`.

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

Nu tvingas PHP-skriptet att använda förberedda satser på databasnivå, vilket förhindrar alla typer av SQL-injektion.

Förhindra Genom Användning Av WAF

Du kan även skydda webbapplikationer från SQL-injektion genom att använda WAF (web application firewall).

WAF skyddar inte bara mot SQL-injektion utan även många andra säkerhetsproblem i lager 7, som cross-site scripting, trasig autentisering, cross-site forgery, dataexponering med mera. Du kan antingen använda en egen värd som Mod Security eller en molnbaserad lösning.

SQL-Injektion och Moderna PHP-Ramverk

SQL-injektion är så vanligt, enkelt, frustrerande och farligt att alla moderna PHP-webbramverk har inbyggda motåtgärder. I WordPress har vi till exempel funktionen `$wpdb->prepare()`, medan MVC-ramverk sköter allt det jobbiga åt dig. Det kan vara lite irriterande att du måste förbereda uttalanden explicit i WordPress, men det är ju WordPress vi pratar om. 🙂

Min poäng är att moderna webbutvecklare inte behöver tänka på SQL-injektion, och som ett resultat kanske de inte ens är medvetna om risken. Även om de lämnar en bakdörr öppen i sin applikation (kanske en $_GET frågeparameter och gamla vanor att köra en oren fråga), kan resultaten bli katastrofala. Det är alltid bra att ta sig tid att förstå grunderna.

Slutsats

SQL-injektion är en allvarlig attack mot webbapplikationer, men den är lätt att undvika. Som vi har sett i den här artikeln, handlar det mest om att vara försiktig när du bearbetar användarinmatning (SQL-injektion är inte det enda hotet som hantering av användarinmatning medför) och att hantera databasfrågor korrekt. Eftersom vi inte alltid jobbar med säkerheten i ett webbramverk är det bättre att vara medveten om denna typ av attack för att inte falla offer för den.