Suchen
Inside Wiki
Nützliche Links




 
phpforum.de bei Facebook
 
phpforum.de bei Twitter
 

Zurück   PHP Forum: phpforum.de > phpforum.de Wiki > phpforum.de Wiki

PHP Wiki Dieses Wiki sammelt Lösungen, zu Problemen, welche immer wieder im Forum auftauchen.

 
 
Artikel-Optionen Ansicht
  #1  

Standard SQL Injections

 

Sicherheit - Inhalte

Das Problem


Eine Reihe von Zeichen verursacht Probleme, wenn man sie unverändert in ein SQL-Statement einbaut. Man muss deshalb zuerst diese Zeichen "escapen". Vergisst man diesen Schritt, dann wird das Skript zur Zielscheibe für SQL-Injektionsangriffe.

(Die Beispiele in diesem Artikel sind alle MySQL-spezifisch, aber das Problem betrifft sämtliche Datenbankschnittstellen.)

Ein Beispiel für einen Angriff


Nehmen wir mal an, eine Site enthält einen geschützten Bereich, für den man sich einloggen muss. Ein schlecht programmiertes Skript fuer das Login-Formular koennte z.B. so aussehen:

Warnung:
DER FOLGENDE CODE IST GEFÄHRLICH!
PHP Quellcode:
$sql = 'SELECT * FROM benutzer WHERE
    user = "'
. $_POST['user'] . '" AND
    pass = "'
. $_POST['pass'] . '"';
$result = mysql_query($sql);
if (mysql_num_rows($result)) {
    // Ja, der Benutzer ist angemeldet und hat Zugang
} else {
    // Nein, der Benutzer existiert nicht
    // oder hat Name oder Passwort falsch eingegeben
}


und zwar nicht nur, weil mit PHP7 die alte mysql-Extension entfernt wurde.

Ein Angreifer könnte diesen Fehler ausbeuten, indem er als Passwort
Code:
Abrakadabra" OR ""="

eingibt. Damit wird aus dem SQL-Statement:
Code:
SELECT * FROM benutzer WHERE
    user = "" AND
    pass = "Abrakadabra" OR ""=""

Die Bedingung ""="" ist immer wahr. Diese Query liefert deshalb sämtliche Datensätze der Tabelle zurück. Die nachfolgende if-Klausel ist plötzlich wahr und so wird der Benutzer als korrekt angemeldet identifiziert, obwohl er weder über Benutzernamen noch Passwort verfügt.

Lösungsmöglichkeiten


Escapen


Jede Datenbankschnittstelle bietet eine Funktion, mit der sich gefährliche Zeichen escapen und damit entschärfen lassen:
PHP Quellcode:
const USER_MIN_LEN = 6; const USER_MAX_LEN = 30;
const PASS_MIN_LEN = 6; const PASS_MAX_LEN = 30;

$user = filter_input(INPUT_POST, 'user', FILTER_CALLBACK, array('options'=>
    function ($user) {
        return strlen($user) >= USER_MIN_LEN &&
               strlen($user) <= USER_MAX_LEN ? $user : FALSE;
    }
));
$pass = filter_input(INPUT_POST, 'pass', FILTER_CALLBACK, array('options'=>
    function ($pass) {
        return strlen($pass) >= PASS_MIN_LEN &&
               strlen($pass) <= PASS_MAX_LEN ? $pass : FALSE;
    }
));

// so geht das Escapen bei der "schlechten", alten und mittlerweile entfernten mysql-Extension:

if ($user && $pass) {
    $sql = 'SELECT * FROM benutzer WHERE
        user = "'
. mysql_real_escape_string($user) . '" AND
        pass = "'
. mysql_real_escape_string($pass) . '"';
    $result = mysql_query($sql);
}

// so geht das bei der neuen mysqli-Extension:

if ($user && $pass) {
    $sql = 'SELECT * FROM benutzer WHERE
        user = "'
. $mysqli->escape_string($user) . '" AND
        pass = "'
. $mysqli->escape_string($pass) . '"';
    $result = $mysqli->query($sql);
}

// und so geht das bei PDO (nicht nur bei MySQL):

if ($user && $pass) {
    $sql = 'SELECT * FROM benutzer WHERE
        user = "'
. $pdo->quote($user) . '" AND
        pass = "'
. $pdo->quote($pass) . '"';
    $result = $pdo->query($sql);
};

Warnung:
Magic Quotes und addslashes() sind kein Ersatz für datenbankspezifische escape-Funktionen. Diese Zeichen werden bei Magic Quotes escaped:
Code:
\x00, \, ' und "

Quelle: http://de.php.net/manual/de/function.addslashes.php

und diese bei mysql_real_escape_string:
Code:
\x00, \x1a, \n, \r, \, ' und "

Quelle: http://de.php.net/manual/de/function...ape-string.php

Magic Quotes und addslashes() sind also unzureichend. (Auch magic_quotes wurden mittlerweile aus PHP entfernt und der Punkt wird hier nur noch aus historischen Gründen benannt.)

Ganzzahlen


stellen einen Sonderfall dar. Einerseits darf und soll man bei ihnen die Anführungszeichen im SQL weglassen und andererseits kann man sich das Escapen sparen, weil die vorkommenden Zeichen harmlos sind. Dazu muss man aber mit absolutiger Sicherheit feststellen, dass der vorliegende Wert auch tatsächlich eine Ganzzahl ist. Zusätzlich sollte man überprüfen, ob die Zahl die zulässigen Werte des Datenbanktyps nicht über- oder unterschreitet. Bei Typen mit vier oder mehr Byte (z.B. MySQL INT und MySQL BIGINT) kann man die Strings möglicherweise nicht mehr in Zahlen umwandeln, da sie ausserhalb des von PHP verwendenten Bereichs liegen könnten. (Die Werte von PHP_INT_MAX und -PHP_INT_MAX sind von System zu System verschieden.) Zum Vergleichen von übergroßen und -kleinen Zahlen kann man die Funktion bccomp benutzen.
PHP Quellcode:
function allowOnlyUnsignedINTs ($i) {
    if (!preg_match('/^\d{1,10}$/', $i)) return FALSE;
    if (bccomp($i, '4294967295') == 1) return FALSE;
    return $i;    
}

function allowOnlyINTs ($i) {
    if (!preg_match('/^-?\d{1,10}$/', $i)) return FALSE;
    if (bccomp($i, '-2147483648') == -1) return FALSE;
    if (bccomp($i, '2147483647') == 1) return FALSE;
    return $i;    
}

$id = filter_input(INPUT_GET, 'id', FILTER_CALLBACK, array('options' =>
    'allowOnlyUnsignedINTs'
));
if ($id !== FALSE) {
    $sql = "SELECT * FROM tabelle WHERE id = $id";
}

// bei MEDIUMINTs und kleineren Typen darf man FILTER_VALIDATE_INT benutzen:

$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT, array('options'=>
    array('min_range' => -8388608, 'max_range' => 8388607)
));
if ($id !== FALSE) {
    $sql = "SELECT * FROM tabelle WHERE id = $id";
}

Reguläre Ausdrücke, Minimal- und Maximalwerte für die Standardtypen von MySQL:
TINYINT/^-?\d{1,3}$/-128127
UNSIGNED TINYINT/^\d{1,3}$/0255
SMALLINT/^-?\d{1,5}$/-3276832767
UNSIGNED SMALLINT/^\d{1,5}$/065535
MEDIUMINT/^-?\d{1,7}$/-83886088388607
UNSIGNED MEDIUMINT/^\d{1,8}$/016777215
INT/^-?\d{1,10}$/-21474836482147483647
UNSIGNED INT/^\d{1,10}$/04294967295
BIGINT/^-?\d{1,19}$/-92233720368547758089223372036854775807
UNSIGNED BIGINT/^\d{1,20}$/018446744073709551615
(Für INTs und BIGINTs sollte bccomp verwendet werden. Bei den kleineren Typen reichen die gewöhnlichen Vergleichsoperatoren.)

Prepared Statements


Bei Prepared Statements zerlegt man die Queries in zwei Teile: SQL und Daten. Im ersten Schritt wird nur das SQL an die Datenbank geschickt und dort vorbereitet, d.h. geparst und optimiert. Im zweiten Schritt werden die Daten dem SQL hinterhergeschickt. Da so SQL und Daten niemals im selben String zusammentreffen, entstehen auch keine Probleme durch sperrige Zeichen. Das Escapen kann man sich hier also sparen:
PHP Quellcode:
const USER_MIN_LEN = 6; const USER_MAX_LEN = 30;
const PASS_MIN_LEN = 6; const PASS_MAX_LEN = 30;

$user = filter_input(INPUT_POST, 'user', FILTER_CALLBACK, array('options'=>
    function ($user) {
        return strlen($user) >= USER_MIN_LEN &&
               strlen($user) <= USER_MAX_LEN ? $user : FALSE;
    }
));
$pass = filter_input(INPUT_POST, 'pass', FILTER_CALLBACK, array('options'=>
    function ($pass) {
        return strlen($pass) >= PASS_MIN_LEN &&
               strlen($pass) <= PASS_MAX_LEN ? $pass : FALSE;
    }
));

// Bei der mysql-Extension gibt's keine prepared Statements!

// Prepared Statements bei mysqli:

// Abschicken des SQLs:
$stmt = $mysqli->prepare(
    'SELECT * FROM benutzer WHERE user = ? AND pass = ?'
);

// Welche Variablen sollen beim execute() benutzt werden?
$stmt->bind_param('ss', $user, $pass);

if ($user && $pass) {
    // Abschicken der Daten:
    $stmt->execute();
}

// Prepared Statements bei PDO:

// Abschicken des SQLs:
$stmt = $dbh->prepare(
    'SELECT * FROM benutzer WHERE user = :user AND pass = :pass'
);

// Welche Variablen sollen beim execute() benutzt werden?
$stmt->bindParam(':user', $user);
$stmt->bindParam(':pass', $pass);
   
if ($user && $pass) {    
    // Abschicken der Daten:
    $stmt->execute();
}


Tipp:
Bei Queries, die mehrmals mit verschiedenen Datensätzen durchgeführt werden, bringen prepared Statements außerdem erhebliche Geschwindigkeitsvorteile, da das Statement nur ein einziges Mal vorbereitet werden muß und anschließend beliebig oft zur Verfügung steht.


Quellen


Doku: Filter Funktionen
Doku: mysql_real_escape_string
Doku: Prepared Statemens bei mysqli
Doku: Prepared Statements bei PDO
Wikipedia: SQL-Injektion
Wikipedia: Prepared Statement

« Vorheriges Kapitel   Sicherheit
  Nächstes Kapitel »

Mitwirkende: Jens Clasen, pecos, combie
Erstellt von pecos, 11.02.2008 am 12:41
Zuletzt bearbeitet von Jens Clasen, 18.04.2016 am 20:06
4 Kommentare , 21242 Betrachtungen

Dieser Text steht unter der GNU-Lizenz für freie Dokumentation


 

Lesezeichen

Stichworte
sicherheit

Artikel-Optionen
Ansicht

Forumregeln
Es ist Ihnen nicht erlaubt, neue Themen zu verfassen.
Es ist Ihnen nicht erlaubt, auf Beiträge zu antworten.
Es ist Ihnen nicht erlaubt, Anhänge hochzuladen.
Es ist Ihnen nicht erlaubt, Ihre Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.

Gehe zu

Alle Zeitangaben in WEZ +2. Es ist jetzt 13:16 Uhr.


Powered by vBulletin® Version 3.8.8 (Deutsch)
Copyright ©2000 - 2018, Jelsoft Enterprises Ltd.
Powered by NuWiki v1.3 RC1 Copyright ©2006-2007, NuHit, LLC