Security Les 4: SQL Injections

Security

XSS, CSRF, SQL Injection

Les 4 / 4 over Security

1 / 13
volgende
Slide 1: Slide
newEditor

In deze les zitten 13 slides, met tekstslides.

Dit is niet oké

Onderdelen in deze les

Security

XSS, CSRF, SQL Injection

Les 4 / 4 over Security


Deze les

Theorie



01

Wat is SQL Injection?

Hoe het ontstaat - het kwetsbare patroon


10 min



02

Impact & aanvalsvormen

Wat kan een aanvaller doen? Blind SQLi, UNION...


8 min



03

Prepared statements

De kernverdediging - hoe het werkt en waarom


12 min



04

ORMs, validatie & least privilege

Extra verdedigingslagen op databaseniveau


8 min



05

Wachtwoorden & foutafhandeling

Hashing - geen plaintext - fail secure in de DB-laag


8 min



06

Verwerking & opdrachten

Kwetsbare queries herkennen en fixen


12 min


Wat is SQL Injection?

SQL Injection = een aanvaller slaagt erin om eigen SQL-code uit te voeren binnen een query van jouw applicatie.


Dit gebeurt wanneer gebruikersinput direct in een SQL-query wordt geplakt - zonder scheiding tussen SQL-code en data.


Kwetsbaar patroon (PHP)


$username = $_GET["username"];


$query = "SELECT * FROM users


WHERE username = '$username'";


$result = mysqli_query($conn, $query);


Normaal: username = jan


Query: SELECT * FROM users WHERE username = "jan"



Aanval: username = " OR "1"="1


Query: SELECT * FROM users WHERE username = "" OR "1"="1";


Wat gaat er mis?


De variabele $username wordt letterlijk in de query geplakt. De database krijgt een grote string en kan niet onderscheiden wat SQL-structuur is en wat data is.


"1"="1" is altijd waar - de query geeft alle gebruikers terug.


Gevanceerde injecties kunnen:


- Data uitlezen uit andere tabellen


- Data aanpassen of verwijderen


- Nieuwe accounts of rechten aanmaken


- Soms het onderliggende systeem benaderen


SQL Injection staat al jaren hoog in de OWASP Top 10 (A03 - Injection). Een van de meest voorkomende kwetsbaarheden.


CASE STUDY - Echte aanvallen uit de praktijk

SQL Injection in de praktijk


130M

kaartgegevens (Heartland 2008)


4M

klantrecords (TalkTalk 2015)


15 jaar

SQL Injection in OWASP Top 10



TalkTalk - 2015 (Groot-Brittannie)

Drie tieners exploiteerden een SQL Injection in de website van telecomprovider TalkTalk.

Ze stalen namen, adressen, geboortedata en bankgegevens van bijna 4 miljoen klanten.

De aanval duurde slechts enkele uren.

Boete van 400.000 GBP van de Britse privacywaakhond.

Hoofd ICT trad af. Aandelenkoers daalde 12% op een dag.



Heartland Payment Systems - 2008 (VS)

Betaalverwerker met honderden miljoenen transacties per jaar.

SQL Injection gaf aanvallers toegang tot het interne netwerk.

130 miljoen creditcard- en betaalpasgegevens gestolen.

Groofste datadiefstal ooit op dat moment.

Schade: meer dan 140 miljoen dollar aan schadevergoedingen.


Les: een query zonder prepared statement kan een heel bedrijf ten val brengen. Geen enkel systeem is te klein om dit fout te doen.


Verdediging: prepared statements

De belangrijkste en meest betrouwbare verdediging tegen SQL Injection.


Het idee achter prepared statements



1

De SQL-structuur wordt vooraf naar de database gestuurd.



2

De data (parameters) worden apart meegegeven.



3

De database behandelt parameters altijd als DATA - nooit als SQL-code.



4

Zelfs als de input " OR "1"="1" bevat - blijft de query veilig.


Scheiding van code en data is het kernprincipe. De database weet al wat de query-structuur is voordat de data binnenkomt.


Kwetsbaar vs veilig (PHP/PDO)


// KWETSBAAR - string concatenatie


$q = "SELECT * FROM users


WHERE username = '$username'";


// VEILIG - prepared statement (PDO)


$stmt = $conn->prepare(


"SELECT * FROM users


WHERE username = :username");


$stmt->execute([


"username" => $username


]);


:username is een parameter - de waarde wordt door de DB als data behandeld, nooit als onderdeel van de query-structuur.


Mysqli ondersteunt ook prepared statements: $stmt = $conn->prepare(); $stmt->bind_param("s", $username);


ORMs, query builders & inputvalidatie

Moderne frameworks helpen - maar je kunt de beveiliging nog steeds omzeilen als je niet oplet.


ORMs & query builders


Tools zoals Laravel Eloquent, Doctrine en Hibernate gebruiken intern prepared statements. Standaard veilig.


// Eloquent - veilig (gebruikt prepared statements intern)


User::where("username", $username)->first();


// Raw SQL in Eloquent - GEVAARLIJK!


DB::select("SELECT * FROM users


WHERE username = '$username'");


Zodra je raw SQL gebruikt, kun je SQL Injection introduceren. Gebruik raw queries alleen als het echt nodig is - en dan met parameters.


Inputvalidatie als extra laag


Prepared statements zijn de kernverdediging. Inputvalidatie is een extra laag bovenop.


Controleer dat een ID een positief getal is


is_int($id) && $id > 0


Controleer dat paginanummers positief zijn


filter_var($page, FILTER_VALIDATE_INT)


Beperk lengte en formaat van invoer


strlen($input) <= 100


Gebruik een whitelist voor bekende waarden


in_array($status, ["actief","inactief"])


Onthoud: inputvalidatie vervangt prepared statements NIET. Het is een extra laag.


Defence in depth: prepared statements + inputvalidatie + least privilege = drie lagen voor de database.


Least privilege & wachtwoorden veilig opslaan


Least privilege op databaseniveau

De databasegebruiker van je webapp moet zo min mogelijk rechten hebben.


Wel geven:


SELECT


INSERT


UPDATE


DELETE


Niet geven:


DROP TABLE


CREATE USER


ALTER DATABASE


GRANT ALL


Waarom? Als er toch een SQL Injection is, kan de aanvaller minder schade aanrichten. Nooit "root" als webapp-gebruiker.


Gebruik aparte DB-gebruikers voor development en productie. Geef de test-omgeving nooit productie-rechten.


Wachtwoorden veilig opslaan


Wachtwoorden mogen NOOIT als plaintext in de database staan.


// FOUT - plaintext opslaan


INSERT INTO users (pass) VALUES ("geheim123")


// JUIST - password_hash() in PHP


$hash = password_hash($pass, PASSWORD_BCRYPT);


// Opslaan: sla $hash op in de database


// Verificatie bij inloggen


if (password_verify($pass, $hash)) {


// inloggen geslaagd


}


password_hash() voegt automatisch een salt toe en gebruikt een sterk algoritme (bcrypt). Sla alleen de hash op - nooit het originele wachtwoord.


Andere gevoelige data (BSN, creditcards): sla alleen op wat echt nodig is. Overweeg versleuteling. Beperk wie erbij kan.


Foutafhandeling & van kwetsbaar naar veilig


Fail secure - geen details weggeven


// FOUT - fout tonen aan gebruiker


echo $e->getMessage();


// toont: SQLSTATE[42000]: Syntax


// error near "OR 1=1"...


// JUIST - generieke foutmelding


// Log intern - geef generiek terug:


echo "Er is iets misgegaan.";


SQL-foutmeldingen bevatten tabelnamen, kolomnamen en query-structuur. Dat is goud voor een aanvaller. Log intern - geef nooit details aan de gebruiker.

Wat wil je loggen?


- Veel mislukte loginpogingen


- Onverwachte databasefouten


- Herhaalde foutieve input


Van kwetsbaar naar veilig - login


// KWETSBAAR


$q = "SELECT * FROM users WHERE


user='$u' AND pass='$p'";


// VEILIG


$stmt = $conn->prepare(


"SELECT password FROM users


WHERE username = :u");


$stmt->execute([":u" => $u]);


$row = $stmt->fetch(PDO::FETCH_ASSOC);


if ($row && password_verify(


$p, $row["password"])) {


// login geslaagd


}


Drie verbeteringen tegelijk: prepared statement + gehashed wachtwoord + geen details bij mislukte login.


OPDRACHT 1 - Kwetsbare query herkennen & fixen

Wat gaat er mis? Hoe fix je het?


// Producten ophalen op basis van ID (PHP)


$id = $_GET["id"];


$q = "SELECT * FROM products


WHERE id = $id";


$r = mysqli_query($conn, $q);


while ($row = mysqli_fetch_assoc($r)) {


echo $row["name"] . $row["price"];


}



1

Leg uit waarom deze code kwetsbaar is voor SQL Injection.

Wat wordt er geplakt in de query?


$id uit de URL wordt direct in de query geplakt. Aanvaller kan bijv. id=1 OR 1=1 invoeren en alle producten terugkrijgen, of erger.



2

Bedenk een voorbeeld van kwaadaardige input voor id.

Denk aan " OR - UNION SELECT - DROP TABLE


Bijv: 1 OR 1=1 (alle rijen) of 0 UNION SELECT username,password,3,4 FROM users-- (gegevens uit andere tabel).



3

Schrijf de query opnieuw met een prepared statement.

Gebruik PDO of mysqli prepared statement


$stmt = $conn->prepare("SELECT * FROM products WHERE id = :id"); $stmt->execute([":id" => $id]); $rows = $stmt->fetchAll();


10 minuten - daarna klassikale bespreking


OPDRACHT 2 - Impact van SQL Injection inschatten

Bedenk per situatie wat een aanvaller kan doen en wat de schade is



🔑 Loginformulier

Aanvaller kan:


- Inloggen zonder wachtwoord (" OR "1"="1")


- Alle gebruikersnamen + wachtwoordhashes uitlezen


- Admin-account overnemen


Schade: Volledige accountovername - datalek van alle gebruikers



🔍 Zoekfunctie in webshop

Aanvaller kan:


- Producttabellen uitlezen inclusief inkoopprijs


- Klantendata ophalen via UNION SELECT


- Prijzen aanpassen via subquery


Schade: Bedrijfsgeheimen gestolen - klantdata gelekt - financiele fraude



⚙️ Adminpagina productbeheer

Aanvaller kan:


- Alle producten verwijderen (DROP / DELETE)


- Eigen account admin-rechten geven


- Logs wissen om sporen te verbergen


Schade: Volledige datastvernietiging - permanente systeemcompromittering


Bespreek: bij welke van de drie situaties is de schade het grootst? Wat was er nodig geweest om de aanval te voorkomen?


OPDRACHT 3 - Je eigen code verbeteren

Zoek in je eigen (oude) projecten een plek waar je user input in een SQL-query gebruikt



A

Gebruik je daar string concatenatie om de query te bouwen?


Kijk naar elke plek waar je een query maakt met $var of variabelen in de string.



B

Kun je dit ombouwen naar een prepared statement?


Gebruik PDO::prepare() en execute() of mysqli::prepare() en bind_param().



C

Welke extra maatregelen zou je toevoegen?


Denk aan: inputvalidatie vooraf - foutafhandeling - least privilege op je DB-gebruiker.



D

Sla je wachtwoorden op als plaintext? Hoe fix je dat?


password_hash() bij opslaan - password_verify() bij inloggen. Nooit zelf hashen met md5 of sha1.


5 minuten individueel - daarna kort klassikaal: wie heeft een kwetsbaarheid gevonden in eigen code?


Afsluiting van de module

Je hebt nu een solide basis in secure coding. Vier onderwerpen, vier verdedigingslagen.



H1

Fundamenten

CIA-model - threat modeling - defence in depth - least privilege - OWASP



H2

XSS & Inputvalidatie

Alles is input - XSS varianten - output encoding - CSP - htmlspecialchars()



H3

CSRF & Sessies

Sessiewerking - CSRF-scenario - CSRF-tokens - SameSite - Secure/HttpOnly



H4

SQL Injection

String concatenatie - prepared statements - least privilege - password_hash()


Je bent nog geen security-specialist - maar je werkt wel bewuster en veiliger. Gebruik dit actief in je projecten: zoek kwetsbaarheden, begrijp ze en verbeter je code.


Samenvatting H4



V

SQL Injection ontstaat als gebruikersinput direct in een query wordt geplakt - zonder scheiding van code en data.



V

Prepared statements zijn de kernverdediging: SQL-structuur vooraf sturen, data apart meegeven als parameter.



V

ORMs zijn standaard veilig - maar raw SQL queries introduceren het risico opnieuw. Gebruik altijd parameters.



V

Least privilege: de DB-gebruiker van je webapp mag geen DROP, CREATE of GRANT uitvoeren.



V

Wachtwoorden nooit als plaintext: gebruik password_hash() bij opslaan en password_verify() bij inloggen.



V

Fail secure: toon geen SQL-foutmeldingen aan gebruikers. Log intern, geef generieke meldingen terug.


Module SEC afgerond!

XSS - CSRF - SQL Injection - Secure coding by design - Blijf dit toepassen in elk project.