Angriffe auf Webanwendungen – Teil 3: SQL-Injection

Vorherige Teile der Serie

Okay, ich schäme mich fast, darüber noch was zu schreiben. Man möchte doch meinen, dass SQL Injection DER bekannteste Angriff überhaupt ist. Möchte man meinen. Bisher hat sichs aber immer noch nicht bis zum letzten Webentwickler rumgesprochen, weswegen ich hier mal 2 konkrete Beispiele liefere, an denen man herumexperimentieren kann.

Was ist SQL-Inection?

Wenn ein Benutzer der Anwendung die Möglichkeit hat, selbst SQL einzuschleusen spricht man von SQL-Injection. Besonders beliebt sind (gerade in älteren Webanwendungen) das Einschleusen von SQL per $_GET. Beispiel gefällig?

Angriffsszenario 1: Der Login

Eigentlich das Musterbeispiel dafür: Folgender Code wird zum validieren eines Login-Formulars verwendet:

<?php $conn = new MySQLi("localhost", "root", "", "userdb");  $sql = "SELECT COUNT(*) AS c FROM users WHERE username='".$_POST['username']."' AND password='".$_POST['password']."'"; $res = $conn->query($sql); $rec = $res->fetch_assoc(); $valid = ($rec['c'] == 1);  if ($valid) { 	//do session stuff... 	header("Location: profile.php"); } else { 	print "Nope, incorrect"; } ?> 

Das Formular (trivial) sieht so aus:

<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="POST"> 	<input type="text" name="username" /> 	<input type="password" name="password" /> 	<input type="submit" value="Anmelden" /> </form> 

Der Benutzer tippt in beide Felder ' OR '1=1, womit die SQL-Abfrage so aussieht: SELECT COUNT(*) AS c FROM users WHERE username='' OR '1=1' AND password='' OR '1=1'

Das ist immer wahr, somit kann sich der Angreifer einloggen, ohne korrekte Zugangsdaten angegeben zu haben.

Angriffsszenario 1: Die Kategorieansicht

Man stelle sich eine Seite vor, die zu einer angegebenen Kategorie-ID den Kategorie-Titel anzeigt. Der Abruf läuft nach dem Schema page.php?catid=3, der Code sieht wie folgt aus:

<?php $conn = new MySQLi("localhost", "root", "", "userdb");  $sql = "SELECT title FROM category WHERE catid = ".$_GET['id']; $res = $conn->query($sql);  while ($rec = $res->fetch_assoc()) { 	print $rec['title']; } 

Ein Angreifer könnte durch den Aufruf von page.php?id=1 UNION SELECT password AS title FROM users mal wieder SQL einschleusen. Damit sieht die Abfrage wie folgt aus:

SELECT title FROM category WHERE catid = 1 UNION SELECT password AS title FROM users 

Damit werden User-Passwörter gleich zur Kategorie mit dazugeliefert – Natürlich muss der Angreifer hier etwas umherexperimentieren, um die korrekten Feld-Namen und Tabellennamen ausfindig zu machen.

Was kann man gegen SQL-Injection tun

Die wirkungsvollste Waffe sind prepared Statements. Diese definieren Platzhalter für erwarteten Input samt erwartetem Datentyp für diesen Platzhalter. So könnte beim Beispiel der Kategorieanzeige von oben kein String eingeschleust werden, weil wir eine Zahl als Datentyp erwarten. Bezogen auf PHP empfiehlt es sich, mit PDO oder mysqli zu arbeiten, die prepared Statements unterstützen. Falls noch wer mit „nacktem“ MySQL arbeitet, sei zu mysql_real_escape_string() geraten. Am Beispiel von mysqli würde eine „Entschärfung“ des ersten Beispiels von oben (Login-Seite) wie folgt aussehen:

$stmt = $conn->prepare("SELECT COUNT(*) AS c FROM users WHERE username=? AND password=?"); //placeholders! $stmt->bind_param('ss', $_POST['username'],$_POST['password']); //two strings are expected -> 'ss' $stmt->bind_result($count); //we expect the result in $count $stmt->execute(); $stmt->fetch(); $valid = ($count == 1); 

Mysqli kümmert sich dann auch freundlicherweise ums korrekte entschärfen der Strings, sodass es keine sql-injection-Möglichkeit mehr gibt. Abschließend möchte ich noch zur deutschen Wikipedia raten, die hier noch weitere schöne Beispielattacken aufführt.

Weitere Teile der Serie


Deprecated: Directive 'allow_url_include' is deprecated in Unknown on line 0