PHP Probleme mit Hash-Abfrage bei Login-Formular

mischaef

Kassettenkind
Teammitglied
Registriert
Aug. 2012
Beiträge
5.930
Einen geruhsamen Sonntag wünsche ich...

Ich versuche gerade einen geschützten Admin-Bereich zu erstellen, der über ein Passwort geschützt sein soll. Irgendwie gibt es da ein Problem mit dem HASH-Vergleich. Folgenden Code habe ich aktuell dafür erstellt:

PHP:
$passwort = htmlspecialchars($_POST['login_passwort']);

    include_once("inc/db.inc.php");

    $mysqli_abfrage = "SELECT
                            benutzer_name, benutzer_passwort
                        FROM
                            benutzer
                        WHERE
                            benutzer_name = '" . htmlspecialchars($_POST['login_name']) . "'";

    if($mysqli_ergebnis = $mysqli_verbindung->execute_query($mysqli_abfrage))
    {
        if($mysqli_ergebnis->num_rows == 0)
        {
            echo "<p>Es wurde kein Benutzer mit diesem Namen gefunden</p>";
        }

        else
        {
            while($datensatz = $mysqli_ergebnis->fetch_assoc())
            {
                if(password_verify($passwort, $datensatz["benutzer_passwort"]))
                {
                    ini_set('session.use_cookies'     ,1);
                    ini_set('session.use_only_cookies',1);
                    ini_set('session.use_trans_sid'   ,0);

                    session_start();

                    $_SESSION['loggedin'] = true;
                    echo "login gestattet";
                }

                else
                {
                    echo "login verwehrt<br>";
                }
            }
        }
    }

Das Problem scheint hier der HASH-Wert in der Datenbank zu sein, den ich mit SELECT password('XXXXXX'); direkt über die Eingabe-Funktion in die Datenbank eingefügt habe. Nur scheint der Vergleich in der Abfrage nicht zu funktionieren. Wenn ich das Passwort im Klartext in die Datenbank schreibe, funktioniert alles wie es soll, die Abfrage usw scheint also richtig zu sein.

Daher die Frage, was da schief gelaufen sein kann oder wie ich den richtigen Hash in die DB bekommen kann? Vielleicht liegt ja da der Fehler. Der bei mir ausgegebene Hash-Wert hat 41 Stellen, mit den eingetragenen varchar(100) sollte ich da also auf der sicheren Seite sein.

Besten dank

Michael
 
Ohne jetzt auf den Rest des Codes einzugehen, dein Problem liegt dran dass du $passwort in deinem Code keinen hash zuordnest, du benutzt lediglich htmlspecialchars.
 
aber ist es nicht so, dass bei password_verify genau das passieren soll? Als das die Funktion den Hash des PW bestimmt und dieses dann mit dem in der DB vergleicht? Ansonsten würde doch ein einfaches if reichen...oder habe ich da einen Denkfehler?
 
  1. Wie der Name schon sagt, fügt man mit SELECT nichts ein, sondern wählt etwas aus. Das entsprechende Kommando für das Einfügen lautet - völlig überraschend - INSERT. Überprüfe mit MySQLAdmin oder einem sonstigen Tool, ob der Wert, den du abfragen willst überhaupt da ist. Und lies dich um Himmels Willen in die Grundlagen von SQL ein, sonst wird das alles nichts.

  2. Prüfe per COUNT wieviele Ergebnisse dein Query zurückliefert.

  3. Der in der DB eingetragene Hash muss mit password_hash() erzeugt worden sein, sonst schlägt ein Vergleich fehl.

  4. htmlspecialchars() ist nicht ausreichend zum Schutz vor Injections. Verwende strip_tags() und zur Sicherheit auch ein str_replace(), das Single Quotes (') escaped (\').
imRa hat hier unrecht, die Verwendung von password_verify() ist - quasi - korrekt.
 
  • Gefällt mir
Reaktionen: mischaef
Das ist korrekt, wir wissen aber nicht wie du das Passwort gespeichert hast. Du kannst alternativ auch einfach mal in Zeile 23 den Datensatz ausgeben und überprüfen ob dort das drinsteckt was du erwartest.

Edit: php ist bei mir schon ein paar Jahre her. Die Funktion funktioniert afaik nur wenn man crypt zum hashen benutzt hat!?
 
@f00bar
Hier geht es um eine Anweisung, bei der ich direkt die Eingabe-Funktion der Datenbank genutzt habe (habe die Anweisung so im Netz gefunden). Dabei wird ein HASH ausgegeben, den ich dann in die DB geschrieben habe. password('Test123') soll dabei den hash "*42434304F38173D0FC396F60C077C662FE6EE7DD" ergeben. Und ich glaube da liegt der Fehler, denn mit der Funktion password_hash kommt dabei "$2y$10$sVnnvKxMRkyv4jGA5s17zewBG/d8vUnwI05rTUFDKrSGxCY2.rL0K" raus - also was ganz anderes. Und das funzt dann auch.

Was lernen wir daraus? Nicht jedem Tutorial vertrauen...^^
 
Nein du lernst hier noch viel merh:

a) das man uU hier das verwendete Tutorial hinschreibt und

b) HASH ohne SALT ist sowas von 1990 mittlerweile

c) du dich dringend! auch mal mit Preperared Statements wegen SQL-Injektions beschäftigen must

Ahoi D.
 
  • Gefällt mir
Reaktionen: Guru-Meditation und MrHeisenberg
Zu c) Die nutze ich normalerweise auch, hier ging es aber zunächst darum, mögliche Fehlerquellen auszuschließen - da ich zu Anfang ja nicht wusste, wo der Fehler lag. Daher habe ich das $_POST direkt eingesetzt. normalerweise würde ich es über

PHP:
$passwort = strip_tags($_POST['login_passwort']);
$name = strip_tags($_POST['login_name']);

    include_once("inc/db.inc.php");

    $mysqli_abfrage = "SELECT
                            benutzer_name, benutzer_passwort
                        FROM
                            benutzer
                        WHERE
                            benutzer_name = ? ";

    if($mysqli_ergebnis = $mysqli_verbindung->execute_query($mysqli_abfrage, [$name]))
    {
        if($mysqli_ergebnis->num_rows == 0)
        {
            echo "<p>Es wurde kein Benutzer mit diesem Namen gefunden</p>";
        }

        else
        {
            while($datensatz = $mysqli_ergebnis->fetch_assoc())
            {
                if(password_verify($passwort, $datensatz["benutzer_passwort"]))
                {
                    ini_set('session.use_cookies'     ,1);
                    ini_set('session.use_only_cookies',1);
                    ini_set('session.use_trans_sid'   ,0);

                    session_start();

                    $_SESSION['loggedin'] = true;
                    echo "login gestattet";
                }

                else
                {
                    echo "login verwehrt<br>";
                }
            }
        }
    }

umsetzen.

str_replace baue ich dann noch ein. Danke für den Hinweis.
 
Warum wird auf den POST Feldern irgendeine Transformation vorgenommen? Sowohl htmlspecialchars als auch strip_tags sind Schwachsinn. Mit den ? Placeholdern werden die Daten bereits sauber von deinem SQL-Befehl getrennt und man muss - und darf - dann nicht mehr in den Daten herumpfuschen.

Etwas seltsam ist die while-Schleife beim Auswerten der SQL-Abfrage. Die Abfrage darf doch niemals mehr als eine Zeile returnen oder? Deine benutzer Tabelle sollte unbedingt einen unique index auf benutzer_name haben um das sicherzustellen.
 
  • Gefällt mir
Reaktionen: Guru-Meditation, [ChAoZ], f00bar und 2 andere
... macht es wiklich Sinn an dem Sample rumzumachen
Marco01_809 schrieb:
Deine benutzer Tabelle sollte unbedingt einen unique index auf benutzer_name haben um das sicherzustellen.
Nur sinnvoll wenn CaseInsensitiv und die Binds auch CaseInsensitiv im SQL behandelt werden wenn es um Benutzernamen geht und warum das Passwort überhaupt die DB verlassen lassen - einfach als Paar prüfen und mann jhat einen Treffer oder keinen.... usw
 
dms schrieb:
warum das Passwort überhaupt die DB verlassen lassen - einfach als Paar prüfen und mann jhat einen Treffer oder keinen.... usw
Weil password_hash/password_verify schon genau die API ist die man nutzen sollte für passwort hashing und in dem hash drinne steht welcher algorithmus zum hashen genutzt wurde, die Daten braucht die verify Funktion. Mit password_needs_rehash kann man dann auch feststellen ob der Algorithmus von dem gespeicherten hash veraltet ist und das passwort neu gesetzt werden sollte.
 
  • Gefällt mir
Reaktionen: Kontrollfreak
Marco01_809 schrieb:
Warum wird auf den POST Feldern irgendeine Transformation vorgenommen? Sowohl htmlspecialchars als auch strip_tags sind Schwachsinn.

Im seinem ersten Codesnippet war von Param Binding nichts zu sehen und da der OP bereits mit diesem Problem nicht wirklich klarkam wollte ich nicht direkt auch noch Param Binding oder Prepared Statements auf die Liste setzen. Dreh mal den Snark ein bisschen zurück!
 
f00bar schrieb:
htmlspecialchars() ist nicht ausreichend zum Schutz vor Injections. Verwende strip_tags() und zur Sicherheit auch ein str_replace(), das Single Quotes (') escaped (\').
Nein! PDO statt mysqli und dann Statements und bindValue!
 
  • Gefällt mir
Reaktionen: Kontrollfreak
Ich kommentiere deinen Code mal, damit du siehst, was du noch besser machen könntest:

PHP:
// Wenn $_POST['login_passwort'] nicht da ist, kommt es zu einer PHP-Notice. Mit dem ??-Operator kann man das umgehen.
// strip_tags entfernt HTML-Tags aus den Variablen - das ist Quatsch
// Ein Passwort wie "mein<br/>Passwort" ist auch gültig, würde so aber zu einem Fehler führen
$passwort = strip_tags($_POST['login_passwort']);
$name = strip_tags($_POST['login_name']);

// ich würde also folgendes schreiben:
// $passwort = $_POST['login_passwort'] ?? "";
// $name = $_POST['login_name'] ?? "";


// includes würde ich immer nach oben machen
// vielleicht auch lieber require_once verwenden, dann bricht das Script ab, wenn die Datei nicht gefunden wird
include_once("inc/db.inc.php");

// benutzer_name musst du nicht mehr aus der Datenbank laden, den hast du ja schon im der Variablen $name
$mysqli_abfrage = "SELECT
                            benutzer_name, benutzer_passwort
                        FROM
                            benutzer
                        WHERE
                            benutzer_name = ? ";

if($mysqli_ergebnis = $mysqli_verbindung->execute_query($mysqli_abfrage, [$name]))
{
    if($mysqli_ergebnis->num_rows == 0)
    {
        // diese Meldung würde ich so anpassen, dass immer folgendes angezeigt wird
       // Benutzername und / oder Passwort falsch
        // Ansonsten kann man rausfinden, welche Benutzernamen es gibt, in dem man durchprobiert
        echo "<p>Es wurde kein Benutzer mit diesem Namen gefunden</p>";
    }

    else
    {
        // das while ist ein bisschen komisch, weil du ja eigentlich nur einen Benutzer haben solltest, zu dem der Login passt
        // problematisch wird das, wenn du mehrere Benutzer mit dem selben Benutzernamen hast, z.B. wenn ein alter Account gesperrt ist
        // ich würde entweder das while weglassen und nur einmal fetch_assoc aufrufen oder im SQL "LIMIT 1" hinzufügen, damit nur ein Datensatz gefunden wird.
        while($datensatz = $mysqli_ergebnis->fetch_assoc())
        {
            if(password_verify($passwort, $datensatz["benutzer_passwort"]))
            {
                // hier verwendest du PHP.ini Einstellungen
                // das macht man entweder einmal in der php.ini-Datei, wenn das nicht gehen sollte, einmal global, aber eher nicht in einer IF-Abfrage
                // zieh das nach oben zu den includes oder pass deine php.ini an
                ini_set('session.use_cookies'     ,1);
                ini_set('session.use_only_cookies',1);
                ini_set('session.use_trans_sid'   ,0);

                // DAS HIER verhindert wahrscheinlich, dass dein Script überhaupt funktioniert
                // session start gehört auch nach oben, sonst startet die session nur beim Login und wird nicht gestartet, wenn du die Seite normal aufrufst - dann könntest du gar nicht prüfen, ob das Login erfolgreich war.
                session_start();

                $_SESSION['loggedin'] = true;

                // statt mit echo zu arbeiten, schreib dir die Nachrichten lieber in eine Variable oder in ein Array und gebe dieses an zentraler Stelle aus. Logik und Ausgabe würde ich trennen.
                echo "login gestattet";
            }

            else
            {
                echo "login verwehrt<br>";
            }
        }
    }
}
 
Zurück
Oben