david: SQL Code-Einschleusung

aus dem php handbuch

...

$query = "SELECT * FROM users WHERE user='{$_POST['username']}' AND password='{$_POST['password']}'";
mysql_query($query);

// Wir haben $_POST['password'] nicht überprüft, könnte alles mögliche enthalten
// was ein User will. Zum Beispiel:
$_POST['username'] = 'aidan';
$_POST['password'] = "' OR 1=1";

// Die gesendete Anfrage an MySQL würde sein:
echo $query;
?>

Gesendete Anfrage an MySQL:

SELECT * FROM users WHERE name='aidan' AND password='' OR 1=1

so, ich versteh zwar das prinzip hinter der code einschleußung
aber ich check noch die "stringgeschichte" noch nicht ganz.

1)$_POST['password'] = "' OR 1=1";

2)$_POST['password'] = '' OR 1=1';
so geht die eischleußung nicht

beim ersten mal wird also ein passwort zu einem code schnipsel gemacht, beim 2ten mal also sprich würde die variable also so heissen
' OR 1=1' was dem hacker nix bringen würde.
oder?

hab ich das richtig verstanden
danke

  1. Hallo!

    Doch, 1 trifft zu und er verschafft sich somit Zugang.
    Denn dann würde das passieren wenn ich mich nicht irre:
    SELECT * FROM users WHERE 1=1

    So muss man nicht Passwort und User abfragen.
    Hab ich das richtig interpretiert?
    Ich hoffe ich erzähl keinen Unfug.

    Grüße, Matze

    1. Doch, 1 trifft zu und er verschafft sich somit Zugang.

      ja so stehts in dem handbuch.
      mir gings bei meiner fragestellung aber eher darum ob ich das generell verstanden habe.
      also:

      was ich <b>doch</b> nicht versteh is:

      $_POST['username'] = 'aidan';
      $_POST['password'] = "' OR 1=1";// hier nimmt das script das Passwort als teil auf.

      warum?

      1. Hallo David,

        ich versteh irgendwie dein Problem nicht, sorry.
        Wenn du eine Abfrage als String zusammensetzt (einer der Gründe, warum man das nach Möglichkeit vermeiden sollte), dann füllt PHP Variablen ein:
        "SELECT name, passwort FROM user WHERE name = '$user' AND passwort = '$passwort'"

        Szenario 1:
        Nutzer gibt "test" und "test" ein
        --> "SELECT name, passwort FROM user WHERE user = 'test' AND passwort = 'test'"
        --> eine syntaktisch korrekte Abfrage die genau den/die Sätze liefert, auf die das zutrifft

        Szenario 2:
        Nutzer gibt "test" und "te"st" ein (äußere Quotes nur zur Verdeutlichung)
        2.1: Das " ist als " maskiert
        --> "SELECT name, passwort FROM user WHERE user = 'test' AND passwort = 'te"st'"
        --> eine syntaktisch korrekte Abfrage die genau den/die Sätze liefert, auf die das zutrifft
        2.2: Das " ist nicht maskiert, da $passwort mit ' begrenzt ist
        hmh, das fällt mir im Moment schwer zu raten was da passiert. Wenn PHP das tatsächlich einfüllt, dann käme da
        "SELECT name, passwort FROM user WHERE user = 'test' AND passwort = 'te"st'"
        raus
        --> ungültiger String, Syntaxfehler, aber ich glaube soweit kämst du nicht

        Szenario 3a, und hier wirds eigentlich interessant
        Nutzer gibt "test" und "te'st" ein
        ohne Escapen ergibt das
        --> "SELECT name, passwort FROM user WHERE user = 'test' AND passwort = 'te'st'"
        --> das ist für PHP ein gültiger String, aber MySQL findet
        SELECT name, passwort FROM user WHERE user = 'test' AND passwort = 'te'st'
        und das ist nicht richtig gequotet

        Szenario 3b:
        "test" und "te''st"
        --> "SELECT name, passwort FROM user WHERE user = 'test' AND passwort = 'te''st'"
        --> für MySQL:
        SELECT name, passwort FROM user WHERE user = 'test' AND passwort = 'te''st'
        Hier stimmt zwar die Anzahl der Quotes, aber ... AND passwort = 'te' ist fertig, das 'st' steht alleine in der Luft --> Syntax-Error

        Szenario 3c
        "test" und "test' OR 1=1"
        --> "SELECT name, passwort FROM user WHERE user = 'test' AND passwort = 'test' OR 1=1"
        --> PHP sagt OK, MySQL kriegt
        SELECT name, passwort FROM user WHERE user = 'test' AND passwort = 'test' OR 1=1
        --> und genau hier ist der Hund begraben:
        AND bindet stärker als OR, die Datenbank versucht also auszuwerten:
        user='test' AND passwort='test' -> liefert Ergebnis oder eben nicht
        ODER
        1=1 -> alle Datensätze erfüllen diese Bedindung
        --> Ergebnis der Abfrage sind also alle name, passwort

        Hilft das?

        MfG
        Rouven

        --
        -------------------
        ie:| fl:| br:> va:| ls:& fo:) rl:( n4:{ ss:) de:] js:| ch:? mo:} zu:|
        1. Hi Rouven,

          2.2: Das " ist nicht maskiert, da $passwort mit ' begrenzt ist
          hmh, das fällt mir im Moment schwer zu raten was da passiert. Wenn PHP das tatsächlich einfüllt, dann käme da
          "SELECT name, passwort FROM user WHERE user = 'test' AND passwort = 'te"st'"
          raus
          --> ungültiger String, Syntaxfehler, aber ich glaube soweit kämst du nicht

          Nix da - PHP setzt da ganz korrekt einfach ein " in den String. Ich glaube du hast da ein kleines Verständnis-Problem. Wenn du den Wert einer Variable im Script festlegen willst, musst du das String-Begrenzer-Zeichen (in diesem Fall also ") maskieren, damit PHP beim parsen weiß, dass der String da noch nicht zu Ende ist:

          $foobar = "blub"blubb";

          Was aber PHP letzendlich speichert ist:

          $foobar => blubb"blubb

          Wenn nun magic_quotes aktiviert sind, werden alle " in $_POST und $_GET Daten durch " ersetzt, im Speicher steht dann also:

          $_POST['foobar'] => blubb"blubb

          Um das im Script zu erreichen müstest du schreiben:

          $_POST['foobar'] => "blubb\"blubb";

          Der erste Backslash maskiert den zweiten und der dritte Backslash maskiert das Anführungszeichen.

          Es kommt also in diesem Fall keineswegs zu einer PHP-Fehlermeldung oder so - nein, es wird korrekt ein MySQL-Query abgesetzt, der aber nach einem Datensatz sucht, wo der Username wirklich foo"bar lautet.

          Szenario 3a, und hier wirds eigentlich interessant
          Nutzer gibt "test" und "te'st" ein
          ohne Escapen ergibt das
          --> "SELECT name, passwort FROM user WHERE user = 'test' AND passwort = 'te'st'"
          --> das ist für PHP ein gültiger String, aber MySQL findet
          SELECT name, passwort FROM user WHERE user = 'test' AND passwort = 'te'st'
          und das ist nicht richtig gequotet

          Richtig, was passiert? Das gibt einen MySQL Fehler, wenn man statt mysql_query(...) immer mysql_query(...) OR die(mysql_error()); schreibt, bekommt man den auch schön angezeigt.

          Und an dieser Stelle wäre nun auch eine SQL Injection problemlos möglich gewesen:

          Nutzer gibt "administrator" und "weißichnicht' OR 1 #" ein
          --> "SELECT name, passwort FROM user WHERE user = '$username' AND passwort = '$password'"
          --> SELECT name, passwort FROM user WHERE user = 'administrator' AND passwort = 'weißichnicht' OR 1 #'

          Durch das OR 1 ist es egal was das Passwort ist, durch das # wird das nachfolgende ' als Kommentar betrachtet (sonst gäbe das nämlich noch einen MySQL-Error) und damit ist es dem Angreifer gelungen sich als administrator einzuloggen, ohne dem sein Passwort zu kennen.

          Szenario 3b:
          "test" und "te''st"
          --> "SELECT name, passwort FROM user WHERE user = 'test' AND passwort = 'te''st'"
          --> für MySQL:
          SELECT name, passwort FROM user WHERE user = 'test' AND passwort = 'te''st'
          Hier stimmt zwar die Anzahl der Quotes, aber ... AND passwort = 'te' ist fertig, das 'st' steht alleine in der Luft --> Syntax-Error

          Falsch - das te''st wird escaped zu te''st, genau das wird für $passwort eingesetzt und MySQL sucht nach einem Benutzer namens te''st - es gibt keinen Syntax-Error.

          Ich glaube, du hast da selber noch ein paar Verständnisprobleme.

          MfG, Dennis.

          1. Hoi!

            2.2: Das " ist nicht maskiert, da $passwort mit ' begrenzt ist
            [...] Nix da - PHP setzt da ganz korrekt einfach ein " in den String. Ich glaube du hast da ein kleines Verständnis-Problem. Wenn du den Wert einer Variable im Script festlegen willst, musst du das String-Begrenzer-Zeichen (in diesem Fall also ") maskieren, damit PHP beim parsen weiß, dass der String da noch nicht zu Ende ist:[...]

            OK, ich sag ja, da war ich mir nicht sicher, die Situation ist mir noch nie untergekommen...

            Falsch - das te''st wird escaped zu te''st, genau das wird für $passwort eingesetzt und MySQL sucht nach einem Benutzer namens te''st - es gibt keinen Syntax-Error.

            ?? von wem? Ich war schon immer noch bei dem Fall wir escapen nichts, führen also das vorherigen Szenario fort und spielen etwas mit den Quotes weiter rum. Wenn das nicht rauskam, mein mea culpa...

            MfG
            Rouven

            --
            -------------------
            ie:| fl:| br:> va:| ls:& fo:) rl:( n4:{ ss:) de:] js:| ch:? mo:} zu:|
          2. Hallo Dennis,

            Nutzer gibt "administrator" und "weißichnicht' OR 1 #" ein
            --> "SELECT name, passwort FROM user WHERE user = '$username' AND passwort = '$password'"
            --> SELECT name, passwort FROM user WHERE user = 'administrator' AND passwort = 'weißichnicht' OR 1 #'

            Durch das OR 1 ist es egal was das Passwort ist, durch das # wird das nachfolgende ' als Kommentar betrachtet (sonst gäbe das nämlich noch einen MySQL-Error) und damit ist es dem Angreifer gelungen sich als administrator einzuloggen, ohne dem sein Passwort zu kennen.

            Warum? Inwiefern? Ich überprüfe hübsch redundant (oder auch nicht)

            • ob die Abfrage genau einen Datensatz zurückliefert.
                Wenn nicht, ist die Anmeldung fehlgeschlagen.

            • ob bei dem zurückgelieferten Datensatz die Kombinationen

            name aus Datensatz, name aus Eingabe
                passwort aus Datensatz, passwort aus Eingabe (gern auch als Hash)

            passen. Passen diese nicht, dann ist die Anmeldung fehlgeschlagen.

            Selbstverständlich sollte man SQL-Injection von vornherein vermeiden. Eine gute Möglichkeit dazu sind der Einsatz von DB-Abstraktionsklassen, die es erlauben, Abfragen zu parametrisieren anstatt sie ad hoc als Zeichenkette zusammenzubauen. Baut man sein SQL-Statement als Zeichenkette zusammen, sollte man (im Falle von MySQL) mysql_real_escape_string() verwenden, dabei magic_quotes berücksichtigen, wie im Handbuch beschrieben.

            Freundliche Grüße

            Vinzenz

            1. Hi Vinzenz,

              Warum? Inwiefern? Ich überprüfe hübsch redundant (oder auch nicht)

              Das machst DU vielleicht - aber du kannst nicht von dir auf einen Anfänger schließen ;-)

              • ob die Abfrage genau einen Datensatz zurückliefert.
                  Wenn nicht, ist die Anmeldung fehlgeschlagen.

              Das _kann_ so sein, _muss_ es aber nicht - wenn nämlich in dem SQL Code noch ein LIMIT 1 mit drinsteckt, dann deckt diese Überprüfung die gegebene SQL-Injection nicht auf. Und LIMIT 1 zu verwenden macht ja durchaus Sinn um die Datenbank etwas zu entlasten.

              • ob bei dem zurückgelieferten Datensatz die Kombinationen

              name aus Datensatz, name aus Eingabe
                  passwort aus Datensatz, passwort aus Eingabe (gern auch als Hash)

              passen. Passen diese nicht, dann ist die Anmeldung fehlgeschlagen.

              Ok, diese Überprüfung ist sehr sinnvoll - ich selber nehme so etwas allerdings nicht vor. Ist aber sicherlich ein top Tip, der in den meisten Tutorials fehlt.

              MfG, Dennis.

              1. Hallo Dennis,

                Das _kann_ so sein, _muss_ es aber nicht - wenn nämlich in dem SQL Code noch ein LIMIT 1 mit drinsteckt, dann deckt diese Überprüfung die gegebene SQL-Injection nicht auf. Und LIMIT 1 zu verwenden macht ja durchaus Sinn um die Datenbank etwas zu entlasten.

                Nö, wieso? Für LIMIT 1 habe ich in dem hier diskutierten Zusammenhang überhaupt kein Verständnis. Nein, auch nicht, um die Datenbank zu entlasten. Wenn schon LIMIT oder TOP (oder was auch immer der SQL-Dialekt hergibt), dann LIMIT 2 :-)

                Begründung:
                Ich erwarte entweder keinen oder einen Datensatz. Kein Datensatz heißt Fehlschlag. Bei einem Datensatz erfolgt meine Zusatzprüfung. Mehr als ein Datensatz bedeutet einen Fehlschlag, zusätzlich aber auch dass ich einen Fehler oder eine Lücke in meinem Code habe. Das ist eine interessante Information, die ich mit LIMIT 2 immer noch erhalte, mit LIMIT 1 jedoch nicht mehr.
                Im Normalfall bringt also ein LIMIT 1 überhaupt keine Entlastung, im Ausnahmefall sorgt LIMIT 1 dafür, dass ich eine Chance, eine Lücke (einen Fehler) in meinem Code zu entdecken, zunichte mache. Selbstverständlich bin ich der Ansicht, dass ich _keinen_ Fehler drin habe, aber warum soll ich Angreifern das Leben leichter machen?

                Freundliche Grüße

                Vinzenz

        2. erstnal herzlichen dank für die mühe:-)

          "SELECT name, passwort FROM user WHERE name = '$user' AND passwort = '$passwort'"

          Szenario 1:
          Nutzer gibt "test" und "test" ein
          --> "SELECT name, passwort FROM user WHERE user = 'test' AND passwort = 'test'"

          Szenario 3c
          "test" und "test' OR 1=1"
          --> "SELECT name, passwort FROM user WHERE user = 'test' AND passwort = 'test' OR 1=1"

          wenn man "test' OR 1=1" in '$passwort' einsetzt hat man

          --> "SELECT name, passwort FROM user WHERE user = 'test' AND passwort = 'test' OR 1=1'"

          bin schon ganz verwirrt

  2. Hi,

    eine Anleitung für SQL injection wirst du hier nicht finden :-)

    Deine Beispiele basieren darauf das die Query einfach erweitert wird, sind alles nur einfache SQL Abfragen, da muss man kein "Hacker" für sein.

    Wenn du dich schützen willst musst du alles was von aussen kommt escapen, oder prüfen was drinnen ist und "smart" escapen.

    Falls du mit PHP baust: http://www.php-faq.de/q/q-sql-injection.html

    Viele Grüße

    Chris