it-swarm.com.de

Wie kann ich Benutzeranmeldeversuche einschränken? PHP

Ich habe gerade diesen Beitrag gelesen Die definitive Anleitung zur formularbasierten Website-Authentifizierung zur Vermeidung von Rapid-Fire-Login-Versuchen. 

Best Practice Nr. 1: Eine kurze Zeitverzögerung, die mit der Anzahl der fehlgeschlagenen Versuche zunimmt, z.

1 fehlgeschlagener Versuch = keine Verzögerung
2 fehlgeschlagene Versuche = 2 s Verzögerung
3 fehlgeschlagene Versuche = Verzögerung von 4 Sekunden
4 fehlgeschlagene Versuche = 8 Sekunden Verzögerung
5 Fehlversuche = 16 Sekunden Verzögerung
usw. 

Ein DoS, der dieses Schema angreift, wäre sehr unpraktisch, aber andererseits verheerend, da die Verzögerung exponentiell zunimmt.

Ich bin gespannt, wie ich so etwas für mein Login-System in PHP implementieren könnte.

53
JasonDavis

Sie können DoS-Angriffe nicht einfach verhindern, indem Sie die Drosselung mit einer einzigen IP-Adresse oder einem einzigen Benutzernamen verketten. Scheiße, mit dieser Methode können Sie nicht einmal schnelle Login-Versuche wirklich verhindern.  

Warum?Da der Angriff mehrere IP-Adressen und Benutzerkonten umfassen kann, um die Throttling-Versuche zu umgehen.

Ich habe an anderen Stellen gesehen, dass Sie im Idealfall alle fehlgeschlagenen Anmeldeversuche auf der Website nachverfolgen und diese mit einem Zeitstempel verknüpfen müssen, vielleicht:

CREATE TABLE failed_logins (
    id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(16) NOT NULL,
    ip_address INT(11) UNSIGNED NOT NULL,
    attempted DATETIME NOT NULL,
    INDEX `attempted_idx` (`attempted`)
) engine=InnoDB charset=UTF8;

Ein kurzer Hinweis zum Feld ip_address: Mit INET_ATON () und INET_NTOA () können Sie die Daten speichern bzw. abrufen, was im Wesentlichen der Umwandlung einer IP-Adresse in eine vorzeichenlose Ganzzahl gleichkommt.

# example of insertion
INSERT INTO failed_logins SET username = 'example', ip_address = INET_ATON('192.168.0.1'), attempted = CURRENT_TIMESTAMP;
# example of selection
SELECT id, username, INET_NTOA(ip_address) AS ip_address, attempted;

Entscheiden Sie sich für bestimmte Verzögerungsschwellen basierend auf der Anzahl gesamt der fehlgeschlagenen Anmeldungen in einer bestimmten Zeitspanne (in diesem Beispiel 15 Minuten). Sie sollten sich auf statistische Daten stützen, die aus Ihrer failed_logins-Tabelle abgerufen werden, da sie zeitliche Änderung basierend auf der Anzahl der Benutzer und der Anzahl der Benutzer, die ihr Passwort abrufen (und eingeben) können.


> 10 failed attempts = 1 second
> 20 failed attempts = 2 seconds
> 30 failed attempts = reCaptcha

Fragen Sie die Tabelle bei jedem fehlgeschlagenen Anmeldeversuch ab, um die Anzahl der fehlgeschlagenen Anmeldungen für einen bestimmten Zeitraum zu ermitteln, beispielsweise 15 Minuten:


SELECT COUNT(1) AS failed FROM failed_logins WHERE attempted > DATE_SUB(NOW(), INTERVAL 15 minute);

Wenn die Anzahl der Versuche über den angegebenen Zeitraum überschritten wird, erzwingen Sie entweder die Drosselung oder zwingen Sie alle Benutzer, ein Captcha (d. H. ReCaptcha) zu verwenden, bis die Anzahl der fehlgeschlagenen Versuche über den angegebenen Zeitraum unter dem Schwellenwert liegt. 

// array of throttling
$throttle = array(10 => 1, 20 => 2, 30 => 'recaptcha');

// retrieve the latest failed login attempts
$sql = 'SELECT MAX(attempted) AS attempted FROM failed_logins';
$result = mysql_query($sql);
if (mysql_affected_rows($result) > 0) {
    $row = mysql_fetch_assoc($result);

    $latest_attempt = (int) date('U', strtotime($row['attempted']));

    // get the number of failed attempts
    $sql = 'SELECT COUNT(1) AS failed FROM failed_logins WHERE attempted > DATE_SUB(NOW(), INTERVAL 15 minute)';
    $result = mysql_query($sql);
    if (mysql_affected_rows($result) > 0) {
        // get the returned row
        $row = mysql_fetch_assoc($result);
        $failed_attempts = (int) $row['failed'];

        // assume the number of failed attempts was stored in $failed_attempts
        krsort($throttle);
        foreach ($throttle as $attempts => $delay) {
            if ($failed_attempts > $attempts) {
                // we need to throttle based on delay
                if (is_numeric($delay)) {
                    $remaining_delay = time() - $latest_attempt - $delay;
                    // output remaining delay
                    echo 'You must wait ' . $remaining_delay . ' seconds before your next login attempt';
                } else {
                    // code to display recaptcha on login form goes here
                }
                break;
            }
        }        
    }
}

Die Verwendung von reCaptcha bei einem bestimmten Schwellenwert würde sicherstellen, dass ein Angriff von mehreren Fronten gestoppt würde und normale Site-Benutzer keine erhebliche Verzögerung für legitime fehlgeschlagene Anmeldeversuche erfahren würden.

74
Corey Ballou

Es gibt drei grundlegende Ansätze: Speichern von Sitzungsinformationen, Speichern von Cookie-Informationen oder Speichern von IP-Informationen.

Wenn Sie Sitzungsinformationen verwenden, kann der Endbenutzer (Angreifer) neue Sitzungen zwangsweise aufrufen, Ihre Taktik umgehen und sich ohne Verzögerung erneut anmelden. Sitzungen sind ziemlich einfach zu implementieren. Speichern Sie einfach die letzte bekannte Anmeldezeit des Benutzers in einer Sitzungsvariablen, vergleichen Sie sie mit der aktuellen Uhrzeit und stellen Sie sicher, dass die Verzögerung lang genug ist.

Wenn Sie Cookies verwenden, kann der Angreifer die Cookies einfach ablehnen. Alles in allem ist dies nicht wirklich machbar.

Wenn Sie IP-Adressen nachverfolgen, müssen Sie Anmeldeversuche von einer IP-Adresse aus speichern, vorzugsweise in einer Datenbank. Wenn ein Benutzer versucht, sich anzumelden, aktualisieren Sie einfach Ihre aufgezeichnete IP-Liste. Sie sollten diese Tabelle in einem angemessenen Intervall löschen und IP-Adressen ausgeben, die seit einiger Zeit nicht aktiv waren. Die Gefahr (es gibt immer eine Gefahr) besteht darin, dass einige Benutzer am Ende eine IP-Adresse freigeben, und unter bestimmten Bedingungen können Verzögerungen die Benutzer unbeabsichtigt beeinflussen. Da Sie fehlgeschlagene Anmeldungen nachverfolgen und nur fehlgeschlagene Anmeldungen durchführen, sollte dies keine zu großen Schmerzen verursachen.

5
Mark Elliot

Der Anmeldeprozess muss seine Geschwindigkeit für eine erfolgreiche und nicht erfolgreiche Anmeldung reduzieren. Der Anmeldeversuch selbst sollte niemals schneller als etwa 1 Sekunde sein. Ist dies der Fall, verwendet Brute Force die Verzögerung, um zu wissen, dass der Versuch fehlgeschlagen ist, da der Erfolg kürzer als der Fehlschlag ist. Dann können mehr Kombinationen pro Sekunde ausgewertet werden. 

Die Anzahl der gleichzeitigen Anmeldeversuche pro Maschine muss vom Load Balancer begrenzt werden. Zum Schluss müssen Sie nur nachverfolgen, ob derselbe Benutzer oder das gleiche Kennwort von mehreren Anmeldeversuchen für Benutzer/Kennwort erneut verwendet wird. Menschen können nicht schneller als etwa 200 Wörter pro Minite schreiben. Nachfolgende oder gleichzeitige Anmeldeversuche, die schneller als 200 Wörter pro Minite sind, stammen also von einer Reihe von Maschinen. Diese können somit sicher auf eine schwarze Liste geleitet werden, da dies nicht Ihr Kunde ist. Die Blacklist-Zeiten pro Host müssen nicht länger als etwa 1 Sekunde sein. Dies wird einen Menschen niemals stören, sondern spielt mit einem rohen Gewaltversuch, entweder in serieller oder paralleler Richtung, Verwüstungen.

2 * 10 ^ 19 Kombinationen mit einer Kombination pro Sekunde, die parallel auf 4 Milliarden separaten IP-Adressen ausgeführt werden, benötigen 158 Jahre als Suchraum. Um einen Tag pro Benutzer gegen 4 Milliarden Angreifer zu bestehen, benötigen Sie ein vollständig zufälliges alphanumerisches Kennwort, das mindestens 9 Stellen lang ist. Betrachten Sie die Schulung von Benutzern in Passphrasen mit mindestens 13 Stellen, 1,7 * 10 ^ 20 Kombinationen.

Diese Verzögerung motiviert den Angreifer, Ihre Kennwort-Hash-Datei zu stehlen, anstatt Ihre Website brutal zu erzwingen. Verwenden Sie genehmigte, benannte Hashtechniken. Das Verbot der gesamten Bevölkerung von Internet-IP für eine Sekunde wird die Auswirkungen paralleler Angriffe einschränken, ohne dass ein Mensch dies zu schätzen weiß. Wenn Ihr System mehr als 1000 fehlgeschlagene Anmeldeversuche in einer Sekunde zulässt, ohne auf das Verbot von Systemen zu reagieren, haben Ihre Sicherheitspläne schließlich größere Probleme. Korrigieren Sie zuerst diese automatisierte Antwort.

4
Don Turnblade
session_start();
$_SESSION['hit'] += 1; // Only Increase on Failed Attempts
$delays = array(1=>0, 2=>2, 3=>4, 4=>8, 5=>16); // Array of # of Attempts => Secs

sleep($delays[$_SESSION['hit']]); // Sleep for that Duration.

oder wie von Cyro vorgeschlagen:

sleep(2 ^ (intval($_SESSION['hit']) - 1));

Es ist ein bisschen grob, aber die Grundkomponenten sind da. Wenn Sie diese Seite aktualisieren, wird die Verzögerung bei jeder Aktualisierung länger.

Sie können die Zähler auch in einer Datenbank aufbewahren, in der Sie die Anzahl der fehlgeschlagenen Versuche nach IP überprüfen. Durch die Verwendung der IP-Adresse und der Beibehaltung der Daten auf Ihrer Seite verhindern Sie, dass der Benutzer seine Cookies löschen kann, um die Verzögerung zu stoppen.

Grundsätzlich wäre der Anfangscode:

$count = get_attempts(); // Get the Number of Attempts

sleep(2 ^ (intval($count) - 1));

function get_attempts()
{
    $result = mysql_query("SELECT FROM TABLE WHERE IP=\"".$_SERVER['REMOTE_ADDR']."\"");
    if(mysql_num_rows($result) > 0)
    {
        $array = mysql_fetch_assoc($array);
        return $array['Hits'];
    }
    else
    {
        return 0;
    }
}
3
Tyler Carter

Speichern Sie Fehlversuche in der Datenbank nach IP. (Da Sie über ein Anmeldesystem verfügen, kann ich davon ausgehen, dass Sie dies genau wissen.)

Natürlich sind Sitzungen eine verlockende Methode, aber jemand, der wirklich engagiert ist, kann leicht feststellen, dass er bei fehlgeschlagenen Versuchen einfach den Sitzungscookie löschen kann, um die Drossel vollständig zu umgehen.

Rufen Sie beim Anmeldeversuch ab, wie viele Anmeldeversuche in letzter Zeit (beispielsweise letzte 15 Minuten) stattgefunden haben, und wann der letzte Versuch stattgefunden hat.

$failed_attempts = 3; // for example
$latest_attempt = 1263874972; // again, for example
$delay_in_seconds = pow(2, $failed_attempts); // that's 2 to the $failed_attempts power
$remaining_delay = time() - $latest_attempt - $delay_in_seconds;
if($remaining_delay > 0) {
    echo "Wait $remaining_delay more seconds, silly!";
}
3
Matchu

IMHO, die Abwehr von DOS-Angriffen wird besser auf Webserver-Ebene (oder sogar in der Netzwerkhardware) behandelt, nicht in Ihrem PHP -Code.

2
vicatcu

Sie können Sitzungen verwenden. Jedes Mal, wenn der Benutzer eine Anmeldung fehlschlägt, erhöhen Sie den Wert, in dem die Anzahl der Versuche gespeichert ist. Sie können die erforderliche Verzögerung anhand der Anzahl der Versuche ermitteln oder die tatsächliche Zeit einstellen, die der Benutzer auch in der Sitzung erneut versuchen darf.

Eine zuverlässigere Methode wäre das Speichern der Versuche und der Zeit des neuen Versuchs in der Datenbank für diese bestimmte IP-Adresse.

2
Sampson

Cookies oder sitzungsbasierte Methoden sind in diesem Fall natürlich unbrauchbar. Die Anwendung muss die IP-Adresse oder die Zeitstempel (oder beide) früherer Anmeldeversuche überprüfen.

Eine IP-Prüfung kann umgangen werden, wenn der Angreifer über mehrere IP-Adressen verfügt, über die er seine Anfragen starten kann. Dies kann problematisch sein, wenn mehrere Benutzer über dieselbe IP eine Verbindung zu Ihrem Server herstellen. Im letzteren Fall würde ein Benutzer, der mehrmals die Anmeldung fehlgeschlagen hat, verhindern, dass sich jeder, der dieselbe IP-Adresse verwendet, für eine bestimmte Zeit mit diesem Benutzernamen anmeldet.

Eine Zeitstempelprüfung hat das gleiche Problem wie oben: Jeder kann verhindern, dass sich alle anderen Benutzer bei einem bestimmten Konto anmelden, indem sie es mehrmals versuchen. Die Verwendung eines Captcha anstelle eines langen Warten auf den letzten Versuch ist wahrscheinlich eine gute Lösung.

Das einzige, was das Login-System verhindern sollte, sind Race-Bedingungen für die Versuchsprüfungsfunktion. Zum Beispiel im folgenden Pseudocode

$time = get_latest_attempt_timestamp($username);
$attempts = get_latest_attempt_number($username);

if (is_valid_request($time, $attempts)) {
    do_login($username, $password);
} else {
    increment_attempt_number($username);
    display_error($attempts);
}

Was passiert, wenn ein Angreifer gleichzeitige Anforderungen an die Anmeldeseite sendet? Wahrscheinlich laufen alle Anforderungen mit derselben Priorität, und es besteht die Möglichkeit, dass keine Anforderung an die Anweisung increment_attempt_number gesendet wird, bevor die anderen über die 2. Zeile hinausgehen. Daher erhält jede Anforderung denselben Wert für $ time und $ Versuche und wird ausgeführt. Das Vermeiden dieser Art von Sicherheitsproblemen kann für komplexe Anwendungen schwierig sein und das Sperren und Entsperren einiger Tabellen/Zeilen der Datenbank mit sich bringen, was die Anwendung natürlich verlangsamt.

1
user225840

Die kurze Antwort lautet: Tun Sie das nicht. Sie werden sich nicht vor brutalem Forcen schützen, Sie könnten Ihre Situation sogar verschlimmern.

Keine der vorgeschlagenen Lösungen würde funktionieren. Wenn Sie die IP als Parameter für die Drosselung verwenden, wird der Angreifer den Angriff auf eine große Anzahl von IP-Adressen aufteilen. Wenn Sie die Sitzung (Cookie) verwenden, wird der Angreifer alle Cookies löschen. Die Summe von allem, was Sie sich vorstellen können, ist, dass es absolut nichts gibt, was ein brutaler Angreifer nicht überwinden konnte.

Es gibt jedoch eine Sache - Sie verlassen sich nur auf den Benutzernamen, der versucht hat, sich anzumelden. Wenn Sie nicht alle anderen Parameter betrachten, verfolgen Sie, wie oft ein Benutzer versucht hat, sich einzuloggen und zu drosseln. Aber ein Angreifer möchte dir schaden. Wenn er dies erkennt, wird er auch Benutzernamen brutal zwingen.

Dies führt dazu, dass fast alle Benutzer beim Einloggen auf den maximalen Wert gedrosselt werden. Ihre Website wird dadurch unbrauchbar. Angreifer: Erfolg.

Sie könnten die Passwortüberprüfung generell um ca. 200ms verzögern - der Websitenutzer wird das fast nicht merken. Aber ein Räuber wird es tun. (Wieder konnte er sich über IPs erstrecken.) Nichts von alledem wird Sie jedoch vor brachialem Forcen oder DDoS schützen - da Sie dies nicht programmatisch tun können.

Die einzige Möglichkeit, dies zu tun, ist die Nutzung der Infrastruktur.

Sie sollten bcrypt anstelle von MD5 oder SHA-x verwenden, um Ihre Kennwörter zu kennzeichnen. Dadurch wird das Entschlüsseln der Kennwörter um ein Vielfaches schwieriger, wenn jemand Ihre Datenbank stiehlt (weil ich denke, dass Sie sich auf einem gemeinsam genutzten oder verwalteten Host befinden).

Es tut uns leid, Sie enttäuscht zu haben, aber alle Lösungen haben eine Schwäche und es gibt keine Möglichkeit, sie in der Back-End-Logik zu überwinden.

1
nico gawenda

Wie oben beschrieben, sind Sitzungen, Cookies und IP-Adressen nicht wirksam - alle können vom Angreifer manipuliert werden.

Wenn Sie Brute-Force-Angriffe verhindern möchten, besteht die einzige praktische Lösung darin, die Anzahl der Versuche auf den angegebenen Benutzernamen zu stützen. Beachten Sie jedoch, dass der Angreifer die Site als DOS-Server verwenden kann, indem er die Anmeldung gültiger Benutzer blockiert.

z.B.

$valid=check_auth($_POST['USERNAME'],$_POST['PASSWD']);
$delay=get_delay($_POST['USERNAME'],$valid);

if (!$valid) {
   header("Location: login.php");
   exit;
}
...
function get_delay($username,$authenticated)
{
    $loginfile=SOME_BASE_DIR . md5($username);
    if (@filemtime($loginfile)<time()-8600) {
       // last login was never or over a day ago
       return 0;
    }
    $attempts=(integer)file_get_contents($loginfile);
    $delay=$attempts ? pow(2,$attempts) : 0;
    $next_value=$authenticated ? 0 : $attempts + 1;
    file_put_contents($loginfile, $next_value);
    sleep($delay); // NB this is done regardless if passwd valid
    // you might want to put in your own garbage collection here
 }

Beachten Sie, dass diese Prozedur wie geschrieben Sicherheitsinformationen verliert - d. H. Es ist möglich, dass jemand, der das System angreift, sieht, wann sich ein Benutzer anmeldet (die Antwortzeit für den Angreiferversuch fällt auf 0). Sie können den Algorithmus auch so einstellen, dass die Verzögerung basierend auf der vorherigen Verzögerung und dem Zeitstempel der Datei berechnet wird.

HTH

C.

1
symcbean

Im Allgemeinen erstelle ich Anmeldungsverlaufs- und Anmeldungsversuchstabellen. In der Versuchstabelle werden Benutzername, Kennwort, IP-Adresse usw. protokolliert. Fragen Sie die Tabelle ab, um zu sehen, ob Sie verzögern müssen. Ich würde das vollständige Blockieren für Versuche empfehlen, die in einer bestimmten Zeit größer als 20 sind (beispielsweise eine Stunde).

1
sestocker

cballuo bot eine ausgezeichnete Antwort. Ich wollte nur die Gunst zurückgeben, indem ich eine aktualisierte Version bereitstelle, die mysqli unterstützt. Ich habe die Tabellen-/Feldspalten in den sqls und anderen kleinen Dingen leicht geändert, aber es sollte jedem helfen, der nach dem mysqli-Äquivalent sucht.

function get_multiple_rows($result) {
  $rows = array();
  while($row = $result->fetch_assoc()) {
    $rows[] = $row;
  }
  return $rows;
}

$throttle = array(10 => 1, 20 => 2, 30 => 5);

$query = "SELECT MAX(time) AS attempted FROM failed_logins";    

if ($result = $mysqli->query($query)) {

    $rows = get_multiple_rows($result);

$result->free();

$latest_attempt = (int) date('U', strtotime($rows[0]['attempted'])); 

$query = "SELECT COUNT(1) AS failed FROM failed_logins WHERE time > DATE_SUB(NOW(), 
INTERVAL 15 minute)";   

if ($result = $mysqli->query($query)) {

$rows = get_multiple_rows($result);

$result->free();

    $failed_attempts = (int) $rows[0]['failed'];

    krsort($throttle);
    foreach ($throttle as $attempts => $delay) {
        if ($failed_attempts > $attempts) {
                echo $failed_attempts;
                $remaining_delay = (time() - $latest_attempt) - $delay;

                if ($remaining_delay < 0) {
                echo 'You must wait ' . abs($remaining_delay) . ' seconds before your next login attempt';
                }                

            break;
        }
     }        
  }
}
0
jason328