Security
XSS, CSRF, SQL Injection
Les 4 / 4 over Security
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.