it-swarm.com.de

Wie sicher ist meine PHP Login-System?

Ich bin neu in PHP und dies ist auch mein erstes Anmeldesystem. Es wäre also großartig, wenn ihr euch meinen Code ansehen und sehen könnt, ob ihr Sicherheitslücken entdeckt:

hinweis: Ich bereinige alle Benutzereingaben, obwohl sie hier nicht angezeigt werden.

Anmelden:

Schritt 1: Ich nehme das vom Benutzer gewählte Passwort und führe es durch diese Funktion:

encrypt($user_chosen_password, $salt);

function encrypt($plain_text, $salt) {
    if(!$salt) {
        $salt = uniqid(Rand(0, 1000000));
    }
    return array(
        'hash' => $salt.hash('sha512', $salt.$plain_text),
        'salt' => $salt
    );
}

Schritt 2: Ich speichere dann den Hash und das Salz ($password['hash'] und $password['salt']) in der Benutzertabelle in der Datenbank:

id | username | password  | salt       | unrelated info...
-----------------------------------------------------------
1  | bobby    | 809a28377 | 809a28377f | ...
                fd131e5934
                180dc24e15
                bbe5f8be77
                371623ce36
                4d5b851e46

Einloggen:

Schritt 1: Ich nehme den Benutzernamen, den der Benutzer eingegeben hat, und sehe in der Datenbank nach, ob Zeilen zurückgegeben werden. Auf meiner Website können keine 2 Benutzer denselben Benutzernamen verwenden, sodass das Feld Benutzername immer einen eindeutigen Wert hat. Wenn ich eine Zeile zurück bekomme, nehme ich das Salz für diesen Benutzer.

Schritt 2: Dann starte ich das vom Benutzer eingegebene Passwort über die Verschlüsselungsfunktion (wie oben angegeben), aber dieses Mal stelle ich auch das aus der Datenbank abgerufene Salz zur Verfügung:

encrypt($user_entered_password, $salt);

Schritt 3: Ich habe jetzt das richtige Passwort für diese Variable: $password['hash']. Also habe ich so ein zweites Mal in der Datenbank nachgeschlagen, um zu sehen, ob der eingegebene Benutzername und das gehashte Passwort zusammen eine einzelne Zeile ergeben. In diesem Fall sind die Anmeldeinformationen des Benutzers korrekt.

Schritt 4: Um den Benutzer nach der Übergabe seiner Anmeldeinformationen einzuloggen, generiere ich eine zufällige eindeutige Zeichenfolge und hashe sie:

$random_string = uniqid(Rand(0, 1000000));
$session_key = hash('sha512', $random_string);

Ich füge dann den $session_key in die active_sessions-Tabelle in der Datenbank ein:

user_id | key
------------------------------------------------------------
1       | 431b5f80879068b304db1880d8b1fa7805c63dde5d3dd05a5b

Schritt 5:

Ich nehme die unverschlüsselte eindeutige Zeichenfolge, die im letzten Schritt generiert wurde ($random_string) und setze sie als Wert eines Cookies, das ich active_session nenne:

setcookie('active_session', $random_string, time()+3600*48, '/');

Schritt 6:

Oben in meinem header.php include steht diese Prüfung:

if(isset($_COOKIE['active_session']) && !isset($_SESSION['userinfo'])) {
   get_userinfo();
}

Die Funktion get_userinfo() führt eine Suche in der Tabelle users in der Datenbank durch und gibt ein assoziatives Array zurück, das in einer Sitzung mit dem Namen userinfo gespeichert ist:

// Zuerst nimmt diese Funktion den Wert des Active_Session-Cookies und hackt ihn, um den Session_Key zu erhalten:

hash('sha512', $random_string);

// dann wird in der active_sessions-Tabelle nachgeschlagen, ob ein Datensatz von dieser key vorhanden ist. In diesem Fall wird der diesem Datensatz zugeordnete user_id abgerufen und in der users-Tabelle ein zweites Mal nachgeschlagen, um die userinfo zu erhalten:

    $_SESSION['userinfo'] = array(
        'user_id'           => $row->user_id,
        'username'          => $row->username,
        'dob'               => $row->dob,
        'country'           => $row->country,
        'city'              => $row->city,
        'Zip'               => $row->Zip,
        'email'             => $row->email,
        'avatar'            => $row->avatar,
        'account_status'    => $row->account_status,
        'timestamp'         => $row->timestamp,
    ); 

Wenn die Sitzung userinfo vorhanden ist, weiß ich, dass der Benutzer authentifiziert ist. Wenn es nicht existiert, aber das active_session-Cookie existiert, wird diese Sitzung durch das Häkchen oben in der header.php-Datei erstellt.

Der Grund, warum ich ein Cookie verwende und nicht nur Sitzungen, ist, dass ich die Anmeldung behalte. Wenn der Benutzer den Browser schließt, ist die Sitzung möglicherweise beendet, der Cookie bleibt jedoch bestehen. Und da dieses Häkchen oben in header.php steht, wird die Sitzung neu erstellt und der Benutzer kann wie gewohnt als angemeldeter Benutzer fungieren.

Ausloggen:

Schritt 1: Sowohl die userinfo-Sitzung als auch der active_session-Cookie sind nicht gesetzt.

Schritt 2: Der zugehörige Datensatz aus der Tabelle active_sessions in der Datenbank wird entfernt.


Anmerkungen: Das einzige Problem, das ich sehen kann (und vielleicht gibt es viele andere), ist, dass der Benutzer diesen active_session-Cookie fälscht, indem er ihn selbst in seinem Browser erstellt. Natürlich müssen sie als den Wert dieses Cookies eine Zeichenfolge festlegen, die nach der Verschlüsselung mit einem Datensatz in der Tabelle active_sessions übereinstimmen muss, aus der ich den user_id abrufen werde, um diese Sitzung zu erstellen. Ich bin mir nicht sicher, wie realistisch die Wahrscheinlichkeit ist, dass ein Benutzer (der möglicherweise ein automatisiertes Programm verwendet) eine Zeichenfolge richtig errät, von der er nicht weiß, dass sie sha512-verschlüsselt und mit der Zeichenfolge in der Tabelle active_sessions in der Datenbank abgeglichen wird um die Benutzer-ID zum Erstellen dieser Sitzung zu erhalten.

Entschuldigen Sie den großen Aufsatz, aber da dies ein so wichtiger Teil meiner Website ist und ich aufgrund meiner Unerfahrenheit nur erfahrenere Entwickler haben wollte, um sicherzustellen, dass er tatsächlich sicher ist.

Sehen Sie Sicherheitslücken in dieser Route und wie kann sie verbessert werden?

38
TK123

Sie sollten eine Art Timeout oder Failover einschließen, um Brute-Force-Angriffe zu verhindern. Es gibt verschiedene Möglichkeiten, dies zu tun, einschließlich IP-basiertes Blockieren, inkrementelle Zeitüberschreitungen usw. Keine dieser Methoden wird jemals stop a hacker sein, aber sie können es viel schwieriger machen.

Ein weiterer Punkt (den Sie nicht erwähnt haben, daher kenne ich Ihren Plan nicht) sind Fehlermeldungen. Machen Sie Fehlermeldungen so vage wie möglich. Die Angabe einer Fehlermeldung wie "Dieser Benutzername ist zwar vorhanden, die Passwörter stimmen jedoch nicht überein" können für den Endbenutzer hilfreich sein, die Funktionalität "it kills login" ist jedoch hilfreich. Sie haben gerade eine Brute-Force-Attacke konvertiert, die O(n^2) Zeit in O(n) + O(n) vergehen sollte. Anstatt alle Permutationen in einer Rainbow-Tabelle (z. B.) auszuprobieren, versucht der Hacker zunächst alle Werte für den Benutzernamen (mit einem gesetzten Kennwort), bis sich die Fehlermeldung ändert. Dann ist es ein gültiger Benutzer know und muss das Passwort nur erzwingen.

In diesem Sinne sollten Sie auch sicherstellen, dass die gleiche Zeit vergeht, wenn ein Benutzername vorhanden ist und nicht vorhanden ist. Sie führen zusätzliche Prozesse aus, wenn tatsächlich ein Benutzername vorhanden ist. Daher wäre die Antwortzeit länger, wenn ein Benutzername vorhanden ist, wenn dies nicht der Fall ist. Ein unglaublich erfahrener Hacker könnte Seitenanfragen zeitlich nach einem gültigen Benutzernamen suchen.

Ebenso sollten Sie sicherstellen, dass Sie nicht nur Cookies ablaufen, sondern auch die Sitzungstabelle verfallen lassen.

Schließlich sollten Sie beim Aufruf get_user_info() alle offenen Sitzungen beenden, wenn mehrere gleichzeitige, aktive Anmeldungen vorhanden sind. Stellen Sie sicher, dass Sie nach einer festgelegten Inaktivitätszeit (z. B. 30 Minuten) Sitzungen abbrechen.

Ganz im Sinne von @Greg Hewgill haben Sie Folgendes nicht hinzugefügt:

  • SSL/verschlüsselte Verbindung zwischen Server-Client
  • Andere Transportprotokolle, die Sie häufig zur Authentifizierung verwenden (wie OAuth)

Sie Server ist sicher, aber es ist unerheblich, wie sicher Ihr Algorithmus ist, wenn jemand die ausgetauschten Daten (MITM) lesen kann. Sie sollten sicherstellen, dass Sie nur über ein verschlüsseltes Protokoll kommunizieren. 

28
sethvargo

... das vom Benutzer eingegebene Passwort über die Verschlüsselungsfunktion ausführen ...

Wie kommt das Passwort vom Browser zum Server? Sie haben den Schutz vor Man-in-the-Middle-Angriffen nicht erwähnt.

8
Greg Hewgill

Dieser Code ...

function encrypt($plain_text, $salt) {
    if(!$salt) {
        $salt = uniqid(Rand(0, 1000000));
    }
    return array(
        'hash' => $salt.hash('sha512', $salt.$plain_text),
        'salt' => $salt
    );
}

...ist schlecht. Verwenden Sie die neue Passwort-API und fertig damit. Wenn Sie kein Experte sind, sollten Sie nicht versuchen, ein eigenes Authentifizierungssystem zu entwerfen. Sie sind extrem schwer zu bekommen .

Um den Benutzer anzumelden, nachdem seine Anmeldeinformationen übergeben wurden, generiere ich eine zufällige eindeutige Zeichenfolge und hasse sie

Einfach lassen Sie PHP die Sitzungsverwaltung übernehmen . Rand() und mt_Rand() sind beide sehr unsichere Zufallszahlengeneratoren.

4

Es scheint, dass der von Ihnen erstellte Code nicht durch automatisierte Einheiten- und Integrationstests getestet werden kann. Dies macht es schwierig, Fehler zu finden, die möglicherweise im Laufe der Zeit und während der Ausführung in einer Produktionsumgebung in Ihrer Implementierung enthalten sind.

Dies führt in der Regel zu Sicherheitsproblemen, da die Sicherheit einer strengen und korrekten Ausführung und der ordnungsgemäßen Datenverarbeitung nicht getestet/überprüft wird.

(Dies ist nur ein weiterer Punkt in der Liste. Siehe auch die Antwort zum Sichern der Transportschicht. Außerdem haben Sie nicht angegeben, wie Sie Ihre Sitzungsdaten vor Manipulation schützen.)

3
hakre

Sie sollten mt_Rand () anstelle von Rand () verwenden.

Hier ist der Grund:

1

In Bezug auf Passwörter in PHP sollten Sie sie nicht verschlüsseln. Sie sollten sie mit dem Befehl password_hash() hashieren und dann beim Anmelden mit password_verify() überprüfen, ob das Kennwort über das HTML-Formular dem gespeicherten Hash in der Datenbank entspricht 

0