Login/Remember-Funktion debuggen

Hallo Leute,

ich sitze gerade an meiner alten Datei namens „functions.php“, welche den Login sowie die Wiederanmeldung via Cookie regelt.
Der Login funktioniert einwandfrei, nur das Wiederanmeldung klappt auf einmal nicht mehr, ohne das mir der Grund bekannt ist.

[PHP]<?php
include_once ‚psl-config.php‘;
include_once ‚db_connect.php‘;
function sec_session_start() {
$session_name = ‚sec_session_id‘; // vergib einen Sessionnamen
$secure = SECURE;
// Damit wird verhindert, dass JavaScript auf die session id zugreifen kann.
$httponly = true;
// Zwingt die Sessions nur Cookies zu benutzen.
if (ini_set(‚session.use_only_cookies‘, 1) === FALSE) {
header(„Location: …/error.php?err=Could not initiate a safe session (ini_set)“);
exit();
}
// Holt Cookie-Parameter.
$cookieParams = session_get_cookie_params();
session_set_cookie_params($cookieParams[„lifetime“],
$cookieParams[„path“],
$cookieParams[„domain“],
$secure,
$httponly);
// Setzt den Session-Name zu oben angegebenem.
session_name($session_name);
session_start(); // Startet die PHP-Sitzung
session_regenerate_id(); // Erneuert die Session, löscht die alte.
}

function login($email, $password, $mysqli) {
// Das Benutzen vorbereiteter Statements verhindert SQL-Injektion.
if ($stmt = $mysqli->prepare(„SELECT user_ID, password, salt
FROM account
WHERE email = ?
LIMIT 1“)) {
$stmt->bind_param(‚s‘, $email); // Bind „$email“ to parameter.
$stmt->execute(); // Führe die vorbereitete Anfrage aus.
$stmt->store_result();
// hole Variablen von result.
$stmt->bind_result($user_ID, $db_password, $salt);
$stmt->fetch();
// hash das Passwort mit dem eindeutigen salt.
$password = hash(‚sha512‘, $password . $salt);
if ($stmt->num_rows == 1) {
// Wenn es den Benutzer gibt, dann wird überprüft ob das Konto
// blockiert ist durch zu viele Login-Versuche
if (checkbrute($user_ID, $mysqli) == true) {
// Konto ist blockiert
// Schicke E-Mail an Benutzer, dass Konto blockiert ist
return false;
} else {
// Überprüfe, ob das Passwort in der Datenbank mit dem vom
// Benutzer angegebenen übereinstimmt.
if ($db_password == $password) {

                // Passwort ist korrekt!
                // Hole den user-agent string des Benutzers.
                $user_browser = $_SERVER['HTTP_USER_AGENT'];
                // XSS-Schutz, denn eventuell wir der Wert gedruckt
                $user_ID = preg_replace("/[^0-9]+/", "", $user_ID);
                $_SESSION['user_ID'] = $user_ID;
                // XSS-Schutz, denn eventuell wir der Wert gedruckt
                $username = preg_replace("/[^a-zA-Z0-9_\-]+/",
                                                            "",
                                                            $username);
                $_SESSION['username'] = $username;
                $_SESSION['login_string'] = hash('sha512',
                          $password . $user_browser);
                $_SESSION['user_ID'] =$user_ID;
                // Login erfolgreich.

                $token = mt_rand(1,999999); // generate a token, should be 128 - 256 bit
                $mysqli->query("UPDATE account SET token=$token
                                WHERE user_ID=$user_ID");
                $cookie = $user_ID . ':' . $token;
                $mac = hash_hmac('sha256', $cookie, 'SECRET_KEY');
                $cookie .= ':' . $mac;
                $mysqli->query("UPDATE account SET rememberme=$cookie WHERE user_ID=$user_ID");
                setcookie('rememberme', $cookie,(time()+(3600*24)*180),'/');

                return true;


            } else {
                // Passwort ist nicht korrekt
                // Der Versuch wird in der Datenbank gespeichert
                $now = time();
                $mysqli->query("INSERT INTO login_attempts(user_ID, time)
                                VALUES ('$user_ID', '$now')");
                return false;
            }
        }
    } else {
        //Es gibt keinen Benutzer.
        return false;
    }
}

}

function rememberMe($mysqli) {
$cookie = isset($_COOKIE[‚rememberme‘]) ? $_COOKIE[‚rememberme‘] : ‚‘;
//Variable bestimmen, isset ist Prüfung ob vorhanden
if ($cookie) { //falls Cookie vorhanden, dann →
list ($user_ID, $token, $mac) = explode(‚:‘, $cookie);
//Aufspaltung des Cookie anhand von „:“ und Variablenebstimmung der Einzelteile
if ($mac !== hash_hmac(‚sha256‘, $user_ID . ‚:‘ . $token, ‚SECRET_KEY‘)) {
//Überprüfung der Gültigkeit des Cookies
return false;
}
$stmt = $mysqli->prepare(„SELECT user_ID, token, password FROM account WHERE user_ID = ? LIMIT 1“);
$stmt->bind_param(‚i‘, $user_ID);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($user_ID, $user_token, $password);
$stmt->fetch();
//Datenbankabfrage des Tokens aus der Datenabnk
if ( timingSafeCompare($user_token, $token) ) {
dologin($user_ID, $mysqli, $password);
}
}
}

function timingSafeCompare($user_token, $token) {
// Prevent issues if string length is 0
$user_token .= chr(0);
$token .= chr(0);

$user_token_len = strlen($user_token);
$tokenlen = strlen($token);

// Set the result to the difference between the lengths
$result = $user_token_len - $tokenlen;

// Note that we ALWAYS iterate over the user-supplied length
// This is to prevent leaking length information
for ($i = 0; $i < $tokenlen; $i++) {
    // Using % here is a trick to prevent notices
    // It's safe, since if the lengths are different
    // $result is already non-0
    $result |= (ord($user_token[$i % $user_token_len]) ^ ord($token[$i]));
}

// They are only identical strings if $result is exactly 0...
return $result === 0;

}

function dologin ($user_ID, $mysqli, $password) {
$user_browser = $_SERVER[‚HTTP_USER_AGENT‘];
// XSS-Schutz, denn eventuell wir der Wert gedruckt
$user_ID = preg_replace(„/[^0-9]+/“, „“, $user_ID);
$_SESSION[‚user_ID‘] = $user_ID;
// XSS-Schutz, denn eventuell wir der Wert gedruckt
$_SESSION[‚login_string‘] = hash(‚sha512‘,
$password . $user_browser);
$_SESSION[‚user_ID‘] =$user_ID;
// Login erfolgreich.
$token = mt_rand(1,999999); // generate a token, should be 128 - 256 bit
$mysqli->query(„UPDATE account SET token=$token
WHERE user_ID=$user_ID“);
$cookie = $user_ID . ‚:‘ . $token;
$mac = hash_hmac(‚sha256‘, $cookie, ‚SECRET_KEY‘);
$cookie .= ‚:‘ . $mac;
setcookie(‚rememberme‘, $cookie,(time()+(3600*24)*180),‚/‘);
}

function login_check($mysqli) {
// Überprüfe, ob alle Session-Variablen gesetzt sind
if (isset($_SESSION[‚user_ID‘], $_SESSION[‚login_string‘])) {
$user_ID = $_SESSION[‚user_ID‘];
$login_string = $_SESSION[‚login_string‘];
// Hole den user-agent string des Benutzers.
$user_browser = $_SERVER[‚HTTP_USER_AGENT‘];
if ($stmt = $mysqli->prepare(„SELECT password
FROM account
WHERE user_ID = ? LIMIT 1“)) {
// Bind „$user_ID“ zum Parameter.
$stmt->bind_param(‚i‘, $user_ID);
$stmt->execute(); // Execute the prepared query.
$stmt->store_result();
if ($stmt->num_rows == 1) {
// Wenn es den Benutzer gibt, hole die Variablen von result.
$stmt->bind_result($password);
$stmt->fetch();
$login_check = hash(‚sha512‘, $password . $user_browser);
if ($login_check == $login_string) {
// Eingeloggt!!!
return true;
} else {
// Nicht eingeloggt
return false;
}
} else {
// Nicht eingeloggt
return false;
}
} else {
// Nicht eingeloggt
return false;
}
} else {
// Nicht eingeloggt
return false;
}
}

function esc_url($url) {
if (‚‘ == $url) {
return $url;
}
$url = preg_replace(‚|[^a-z0-9-~+_.?#=!&;,/:%@$|*'()\x80-\xff]|i‘, ‚‘, $url);
$strip = array(‚%0d‘, ‚%0a‘, ‚%0D‘, ‚%0A‘);
$url = (string) $url;
$count = 1;
while ($count) {
$url = str_replace($strip, ‚‘, $url, $count);
}
$url = str_replace(‚;//‘, ‚://‘, $url);
$url = htmlentities($url);
$url = str_replace(‚&‘, ‚&‘, $url);
$url = str_replace(„'“, ‚‘', $url);
if ($url[0] !== ‚/‘) {
// Wir wollen nur relative Links von $_SERVER[‚PHP_SELF‘]
return ‚‘;
} else {
return $url;
}
}[/PHP]

Im speziellen geht es dabei um die Funktionen „dologin“ und „rememberme“. Das Problem ist jetzt, wie kriege ich bei einer solche Struktur raus, wo der Fehler liegt?
Ich dachte vielleicht an eine separate Seite, in der ich mir die Ergebnisse der Teilschritte via Echo oder var_dump ausgeben lassen.
Oder habt ihr da vielleicht einen anderen Lösungsvorschlag?

Richtig debuggen

  1. Man bemerkt, dass ein Skript nicht das tut, was es soll.
  2. Man schreibt an den Anfang des Scriptes die Zeile: error_reporting(-1);
  3. Man verwendet ini_set(‚display_errors‘, true); damit die Fehler auch angezeigt werden.
  4. Man versucht, die Stelle die daran Schuld sein kann, schonmal einzugrenzen. Falls dies nicht geht, wird zunächst das komplette Skript als fehlerhaft angesehen.
  5. An markanten Stellen im Skript lässt man sich wichtige Variableninhalte ausgeben und ggf. auch in bedingten Anweisungen eine kurze Ausgabe machen, um zu überprüfen, welche Bedingung ausgeführt wurde. Wichtig bei MySQL Fehlern (…not a valid MySQL result resource…): mysqli_error() verwenden oder Abfrage ausgeben und zb mit phpmyadmin testen.
  6. Schritt 5 wird so lange wiederholt, bis Unstimmigkeiten im Skript auffallen
  7. Damit hat man das Problem (Unstimmigkeit) gefunden und kann versuchen diese zu beheben. Hierzu dienen dann die PHP-Dokumentation und andere Quellen als Ratgeber.
  8. Lässt sich das konkrete Problem trotzdem nicht beheben, kann man in Foren um Rat fragen.
  9. Das Programm läuft und man kann die Debug-Ausgaben wieder entfernen.

Den Text kannte ich zwar schon, aber die Erinnerung war gut! Danke :slight_smile:

Also ich habe das jetzt zum Teil bereits getestet und bisher keinen einzigen Fehler gefunden.
Im Gegenteil, eigentlich funktioniert alles.
Seltsamerweise muss ich die Seite nur nochmal laden, wenn ich auf der Login-Seite bin, dann meldet es mich automatisch an und leitet mich auf die richtige Seite weiter …
ergo muss da noch irgendwas in der Abfolge falsch sein.
Aber das ist ein anderes Problem.

EDIT//
Manchmal kann die Welt so einfach sein :slight_smile: Es lag einfach nur an einer fehlerhaften Weiterleitung. Ich nahm an, dass das Script mich wegen falschen Cookies oder sonst irgendetwas immer wieder auf die Login-Seite schickt.
Stattdessen lag der Fehler ganz wo anders!

Aber trotzdem ein Dank an bdt600, der Debugging-Text liegt jetzt erstmal auf dem Desktop :slight_smile:

Kann man seit neuesten seinen Post nach längerer Zeit nicht mehr bearbeiten?

Also der Fehler tritt jetzt schon wieder auf und ich weiß gerade einfach nicht, was diesen auslöst.
Die Wiederanmeldung klappt nur am selben Tag, einen oder mehrere Tager später geht es nicht mehr. Der Cookie ist dann seltsamerweise ungültig.
Hier der Code der Funktion „rememberMe“ der
[PHP]
function rememberMe($mysqli) {
$cookie = isset($_COOKIE[‚rememberme‘]) ? $_COOKIE[‚rememberme‘] : ‚‘;
//Variable bestimmen, isset ist Prüfung ob vorhanden, aber das ? … sagt mir nix
if ($cookie) { //falls Cookie vorhanden, dann →
list ($user_ID, $token, $mac) = explode(‚:‘, $cookie);
echo „“;
//Aufspaltung des Cookie anhand von „:“ und Variablenebstimmung der Einzelteile
if ($mac !== hash_hmac(‚sha256‘, $user_ID . ‚:‘ . $token, ‚SECRET_KEY‘)) {
//Überprüfung der Gültigkeit des Cookies
echo „“;
return false;
}
else { echo „“;}
$stmt = $mysqli->prepare(„SELECT user_ID, token, password FROM account WHERE user_ID = ? LIMIT 1“);
$stmt->bind_param(‚i‘, $user_ID);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($user_ID, $user_token, $password);
$stmt->fetch();
//Datenbankabfrage des Tokens aus der Datenabnk
if ( timingSafeCompare($user_token, $token) ) {
dologin($user_ID, $mysqli, $password);
}
}
}
[/PHP]

In der Console steht:
„Cookie vorhanden“
„Cookie ungültig“

Nach erneutem anmelden müsste es danach aber erstmal wieder funktionieren oder nicht? :wink:

Wenn ich den Cookie per Hand lösche und mich wieder anmelde, funktioniert es.
Den ganzen Tag … bis morgen … und dann nicht mehr

Du hast wohl schon eine Vermutung?

Wenn würde ich hier ansetzen:

Cookies werden ausschließlich vom Client verwaltet. Somit entscheidet der Client, ob beispielsweise ein Cookie gespeichert oder nach der vom Webserver gewünschten Lebensdauer wieder gelöscht wird.

… aber anscheinend ist dein Cookie nach wie vor noch vorhanden, wenn du Ihn per Hand löschen musst.

Ja der Cookie ist vorhanden.
Kann es vielleicht an Xampp liegen?

In der Zeile wird der Cookie geprüft:
[PHP]
if ($mac !== hash_hmac(‚sha256‘, $user_ID . ‚:‘ . $token, ‚SECRET_KEY‘)) {
//Überprüfung der Gültigkeit des Cookies
echo „“;
return false;
}
else { echo „“;}
[/PHP]
Ergebnis: Cookie ungültig

Haste dir mal $mac und das Ergebnis von hash_hmac ausgeben lassen? Auf den ersten Blick kann ich nichts erkennen. Konnte den Code grad leider nur überfliegen.

Ich lass das jetzt erstmal in der Ecke liegen und bau an anderer Stelle um.
Wahrscheinlich muss ich es eh nochmal umbauen.
Und zur Not würden mir sicherlich auch einige von euch für einen angemessenen Betrag das Modul bauen. :slight_smile:

Wenn jemand die Zeit dafür hätte. Ich zumindest nicht. :stuck_out_tongue:

Ich bin der festen Überzeugung das du das auch alleine noch hinbekommst :slight_smile: Ansonsten schau mal Abends aufm Teamspeak vorbei, dann kann ich dir vllt. über TeamViewer helfen. :wink:

Ich wollte damit nur zeigen, dass ich eure Mühe auch finanziell ausgleichen würde :slight_smile:

Ja das sollte prinzipiell auch kein großes Problem sein, nur will ich mich aktuell nicht die ganze Zeit mit diesem Feature beschäftigen/aufhalten. Daher schau ich später noch drauf.
Sinnvoll wäre es ja, erstmal das Feature als Standalone-Modul zu bauen und es damit zu testen.

Gerne schaue ich auch mal auf dem Teamspeaker-Server vorbei :slight_smile:

Schreib mir einfach eine PN, dann lasse ich dir die Daten zukommen. :wink: So back to topic.