Während die Berichte variieren, berichtete die Washington Post, dass sich das Foto-Hacking von Prominenten bei iCloud um den ungeschützten Login-Punkt von Find My iPhone drehte:
"… Sicherheitsforschern soll ein Fehler in der iCloud-Funktion" Find My iPhone "gefunden worden sein, der Brute-Force-Angriffe nicht verhindert hat. Die Aussage von Apple… legt nahe, dass das Unternehmen diese Offenbarung nicht als Problem betrachtet. Und das ist ein Problem an den Sicherheitsforscher und den Beitrag der Washington Post, Ashkan Soltani.
Genau. Ich wünschte, Apple wäre offener geworden; Seine sorgfältig formulierte Antwort ließ Raum für unterschiedliche Interpretationen und schien die Opfer zu tadeln.
Hacker haben dieses iBrute-Skript möglicherweise auf GitHub verwendet, um über Mein iPhone nach Prominenten zu suchen. Die Schwachstelle wurde inzwischen geschlossen.
Da eines der reichsten Unternehmen der Welt nicht die Ressourcen für die Ratenbegrenzung aller Authentifizierungspunkte zugewiesen hat, ist es wahrscheinlich, dass einige Ihrer Web-Apps keine Ratenbegrenzung enthalten. In diesem Lernprogramm gehe ich durch einige der grundlegenden Konzepte der Ratenbegrenzung und einer einfachen Implementierung für Ihre PHP-basierte Webanwendung.
Die Forschung aus früheren Hacks hat Passwörter offengelegt, die die Leute am häufigsten verwenden. Xeno.net veröffentlicht eine Liste der zehntausendsten Passwörter. Die folgende Tabelle zeigt, dass die Häufigkeit der häufigsten Passwörter in ihrer Top-100-Liste 40% beträgt und die Top-500-Adressen 71% ausmachen. Mit anderen Worten, Menschen verwenden und verwenden häufig eine kleine Anzahl von Passwörtern. Zum Teil, weil sie leicht zu merken und leicht zu tippen sind.
Das bedeutet, dass selbst ein winziger Wörterbuchangriff, der nur die fünfundzwanzig gebräuchlichsten Kennwörter verwendet, beim Targeting von Diensten recht erfolgreich sein kann.
Sobald ein Hacker einen Einstiegspunkt identifiziert, an dem unbegrenzte Anmeldeversuche möglich sind, können Angriffe mit hoher Geschwindigkeit und großem Volumen automatisiert werden. Wenn es keine Geschwindigkeitsbegrenzung gibt, ist es für Hacker leicht, mit immer größeren Wörterbüchern anzugreifen - oder mit automatisierten Algorithmen mit unendlich vielen Permutationen.
Wenn darüber hinaus persönliche Informationen über das Opfer bekannt sind, z. Der Name ihres Partners oder Haustiers, ein Hacker, kann Angriffe auf Permutationen wahrscheinlicher Passwörter automatisieren. Dies ist eine häufige Verwundbarkeit für Prominente.
Um Logins zu schützen, gibt es einige Ansätze, die ich als Basis empfehle:
In beiden Fällen möchten wir fehlgeschlagene Versuche in einem bestimmten Zeitfenster oder Zeitfenster messen, z. 15 Minuten und 24 Stunden.
Ein Risiko beim Blockieren von Versuchen nach Benutzernamen besteht darin, dass der tatsächliche Benutzer aus seinem Konto gesperrt werden kann. Wir möchten also sicherstellen, dass der gültige Benutzer sein Konto wieder öffnen und / oder sein Kennwort zurücksetzen kann.
Ein Risiko für das Blockieren von Versuchen nach IP-Adressen besteht darin, dass sie häufig von vielen Benutzern gemeinsam genutzt werden. Beispielsweise kann eine Universität sowohl den tatsächlichen Kontoinhaber als auch jemanden beherbergen, der versucht, sein Konto böswillig zu hacken. Das Blockieren einer IP-Adresse kann den Hacker sowie den tatsächlichen Benutzer blockieren.
Ein Kostenfaktor für die Erhöhung der Sicherheit ist jedoch oft ein bisschen mehr Unannehmlichkeiten. Sie müssen entscheiden, wie streng Ihre Preise begrenzt werden sollen und wie einfach es für Benutzer möglich sein soll, ihre Konten wieder zu öffnen.
Es kann hilfreich sein, eine geheime Frage in Ihre App zu codieren, mit der ein Benutzer, dessen Konto gesperrt wurde, erneut authentifiziert werden kann. Alternativ können Sie ein Kennwort an die E-Mail-Adresse zurücksetzen (in der Hoffnung, dass es nicht gefährdet wurde)..
Ich habe ein bisschen Code geschrieben, um Ihnen zu zeigen, wie Sie Ihre Webanwendungen einschränken können. Meine Beispiele basieren auf dem Yii Framework für PHP. Der größte Teil des Codes ist auf alle PHP / MySQL-Anwendungen oder -Frameworks anwendbar.
Zunächst müssen wir eine MySQL-Tabelle erstellen, um Informationen von fehlgeschlagenen Anmeldeversuchen zu speichern. Der Tisch sollte das speichern IP Adresse
des anfragenden Benutzers, der versuchte Benutzername oder die verwendete E-Mail-Adresse sowie ein Zeitstempel:
$ this-> createTable ($ this-> tableName, array ('id' => 'pk', 'ip_address' => 'Zeichenfolge NOT NULL', 'Benutzername' => 'Zeichenfolge NOT NULL', 'created_at' => 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',), $ this-> MySqlOptions);
Anschließend erstellen wir ein Modell für die LoginFail-Tabelle mit mehreren Methoden: Hinzufügen, Prüfen und Löschen.
Wenn eine Anmeldung fehlgeschlagen ist, fügen wir der LoginFail-Tabelle eine Zeile hinzu:
public function add ($ username) // fügt der fehlgeschlagenen Login-Tabelle eine Zeile mit Benutzername und IP-Adresse hinzu $ failure = new LoginFail; $ failure-> Benutzername = $ Benutzername; $ failure-> ip_address = $ this-> getUserIP (); $ failure-> created_at = new CDbExpression ('NOW ()'); $ failure-> save (); // Wenn eine Anmeldung fehlgeschlagen ist, lösche älteren Fehler log $ this-> purge ();
Zum getUserIP ()
, Ich habe diesen Code von Stack Overflow verwendet.
Wir können auch die Möglichkeit einer fehlgeschlagenen Anmeldung nutzen, um die Tabelle älterer Datensätze zu löschen. Ich mache das, um zu verhindern, dass die Verifizierungsprüfungen im Laufe der Zeit nachlassen. Oder Sie können eine Bereinigungsoperation stündlich oder täglich in einer Hintergrund-Cron-Task implementieren:
öffentliche Funktion bereinigen ($ min = 120) // Fehlgeschlagene Anmeldeeinträge löschen, die älter sind als $ min. $ minutes_ago = (time () - (60 * $ min)); // z.B. Vor 120 Minuten $ kriterien = neue CDbCriteria (); LoginFail :: model () -> älter_than ($ minutes_ago) -> applyScopes ($ kriterien); LoginFail :: model () -> deleteAll ($ kriterien);
Das Yii-Authentifizierungsmodul, das ich verwende, sieht folgendermaßen aus:
public function authenticate ($ attribut, $ params) if (! $ this-> hasErrors ()) // wir wollen nur authentifizieren, wenn keine Eingabefehler $ identity = new UserIdentity ($ this-> username, $ this->) Passwort); $ identity-> authenticate (); if (LoginFail :: model () -> check ($ this-> username)) $ this-> addError ("username", UserModule :: t ("Kontozugriff ist gesperrt, wenden Sie sich an den Support.")); else switch ($ identity-> errorCode) case UserIdentity :: ERROR_NONE: $ duration = $ this-> rememberMe? Yii :: app () -> Controller-> Modul-> rememberMeTime: 0; Yii :: app () -> user-> login ($ identity, $ duration); brechen; case UserIdentity :: ERROR_EMAIL_INVALID: $ this-> addError ("Benutzername", UserModule :: t ("E-Mail ist falsch.")); LoginFail :: model () -> add ($ this-> Benutzername); brechen; case UserIdentity :: ERROR_USERNAME_INVALID: $ this-> addError ("Benutzername", UserModule :: t ("Benutzername ist falsch.")); LoginFail :: model () -> add ($ this-> Benutzername); brechen; case UserIdentity :: ERROR_PASSWORD_INVALID: $ this-> addError ("Kennwort", UserModule :: t ("Kennwort ist falsch.")); LoginFail :: model () -> add ($ this-> Benutzername); brechen; case UserIdentity :: ERROR_STATUS_NOTACTIV: $ this-> addError ("status", UserModule :: t ("Ihr Konto ist nicht aktiviert.")); brechen; case UserIdentity :: ERROR_STATUS_BAN: $ this-> addError ("status", UserModule :: t ("Ihr Konto ist gesperrt.")); brechen;
Immer wenn mein Login-Code einen Fehler feststellt, rufe ich die Methode auf, um Details zur LoginFail-Tabelle hinzuzufügen:
LoginFail :: model () -> add ($ this-> Benutzername);
Der Überprüfungsabschnitt ist hier. Dies läuft bei jedem Anmeldeversuch ab:
$ identity-> authenticate (); if (LoginFail :: model () -> check ($ this-> username)) $ this-> addError ("username", UserModule :: t ("Kontozugriff ist gesperrt, wenden Sie sich an den Support."));
Sie können diese Funktionen in den Anmelde-Authentifizierungsbereich Ihres eigenen Codes integrieren.
Meine Verifizierungsprüfung sucht nach einer großen Anzahl fehlgeschlagener Anmeldeversuche für den betreffenden Benutzernamen und getrennt nach der verwendeten IP-Adresse:
Öffentliche Funktionsprüfung ($ username) // prüft, ob der Schwellenwert für fehlgeschlagene Anmeldung überschritten wurde // für den Benutzernamen in den letzten 15 Minuten und die letzte Stunde // und für die IP-Adresse in den letzten 15 Minuten und die letzte Stunde $ has_error = false; $ minutes_ago = (Zeit () - (60 * 15)); // Vor 15 Minuten $ hours_ago = (time () - (60 * 60)); // vor 1 Stunde $ user_ip = $ this-> getUserIP (); if (LoginFail :: model () -> since ($ minutes_ago) -> Benutzername ($ username) -> count ()> = self :: FAILS_USERNAME_QUARTER_HOUR) $ has_error = true; else if (LoginFail :: model () -> since ($ minutes_ago) -> ip_address ($ user_ip) -> count ()> = self :: FAILS_IP_QUARTER_HOUR) $ has_error = true; else if (LoginFail :: model () -> since ($ hours_ago) -> Benutzername ($ username) -> count ()> = self :: FAILS_USERNAME_HOUR) $ has_error = true; else if (LoginFail :: model () -> since ($ hours_ago) -> ip_address ($ user_ip) -> count ()> = self :: FAILS_IP_HOUR) $ has_error = true; if ($ has_error) $ this-> add ($ username); return $ has_error;
Ich überprüfe Ratengrenzen für die letzten 15 Minuten sowie für die letzte Stunde. In meinem Beispiel erlaube ich 3 fehlgeschlagene Anmeldeversuche pro fünfzehn Minuten und sechs pro Stunde für jeden angegebenen Benutzernamen:
const FAILS_USERNAME_HOUR = 6; const FAILS_USERNAME_QUARTER_HOUR = 3; const FAILS_IP_HOUR = 24; const FAILS_IP_QUARTER_HOUR = 12;
Beachten Sie, dass meine Überprüfungsprüfungen die benannten Bereiche von ActiveRecord von Yi verwenden, um den Abfragecode der Datenbank zu vereinfachen:
// Gültigkeitsbereich von Zeilen seit der Zeitstempel public function since ($ tstamp = 0) $ this-> getDbCriteria () -> mergeWith (array ('condition' => '(UNIX_TIMESTAMP (created_at)>'. $ tstamp. ')' ,)); $ dieses zurückgeben; // Gültigkeitsbereich der Zeilen vor der öffentlichen Zeitmarke für timestamp (_tstamp = 0) $ this-> getDbCriteria () -> mergeWith (array ('condition' => '(UNIX_TIMESTAMP (created_at))<'.$tstamp.')', )); return $this; public function username($username=") $this->getDbCriteria () -> mergeWith (array ('condition' => '(username = "'. $ username. '")',)); $ dieses zurückgeben; public function ip_address ($ ip_address = ") $ this-> getDbCriteria () -> mergeWith (array ('condition' => '(ip_address ="'. $ ip_address. '"));) $ zurückgeben
Ich habe versucht, diese Beispiele so zu schreiben, dass Sie sie leicht anpassen können. Sie können beispielsweise die Überprüfungen für die letzte Stunde weglassen und sich auf das letzte 15-Minuten-Intervall verlassen. Alternativ können Sie die Konstanten ändern, um höhere oder niedrigere Schwellenwerte für die Anzahl der Anmeldungen pro Intervall festzulegen. Sie könnten auch viel ausgefeiltere Algorithmen schreiben. Es liegt an dir.
In diesem Beispiel möchten Sie zur Verbesserung der Leistung die LoginFail-Tabelle nach Benutzernamen und getrennt nach IP-Adresse indizieren.
Mein Beispielcode ändert den Status von Konten nicht wirklich in "Gesperrt" oder stellt Funktionen zum Entsperren bestimmter Konten bereit. Ich überlasse Ihnen dies. Wenn Sie einen Blockier- und Rücksetzmechanismus implementieren, möchten Sie möglicherweise die Möglichkeit bieten, die IP-Adresse oder den Benutzernamen separat zu sperren.
Ich hoffe, Sie fanden das interessant und nützlich. Bitte zögern Sie nicht, Korrekturen, Fragen oder Kommentare zu posten. Mich interessieren vor allem alternative Ansätze. Sie können mich auch auf Twitter @reifman erreichen oder mich direkt per E-Mail kontaktieren.
Credits: iBrute-Vorschaubild über Heise Security