Domanda

Stavo leggendo questo messaggio La guida definitiva a-based formare sito web di autenticazione sulla prevenzione Rapid-Fire login tentativi.

Best Practice # 1: Un breve intervallo di tempo che aumenta con il numero di tentativi falliti, come:

1 tentativo fallito = nessun ritardo
2 tentativi falliti = 2 sec di ritardo
3 tentativi falliti = 4 sec ritardo
4 tentativi falliti = 8 sec ritardo
5 tentativi falliti = 16 sec ritardo
ecc

DoS attaccare questo sistema sarebbe molto pratico, ma d'altra parte, potenzialmente devastante, poiché il ritardo aumenta esponenzialmente.

Sono curioso come avrei potuto implementare qualcosa di simile per il mio sistema di accesso in PHP?

È stato utile?

Soluzione

È possibile non solo prevenire gli attacchi DoS concatenando strozzamento fino a un singolo IP o nome utente. L'inferno, non si può nemmeno davvero prevenire i tentativi di accesso a raffica utilizzando questo metodo.

Perché? perché l'attacco può estendersi su più indirizzi IP e gli account utente per il bene di bypassare i vostri tentativi di limitazione.

Ho visto postato altrove, che idealmente si dovrebbe essere il monitoraggio di tutte fallito i tentativi di accesso attraverso il sito e associandoli ad un timestamp, forse:

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;

Una breve nota sul campo ip_address:. È possibile memorizzare i dati e recuperare i dati, rispettivamente, con inet_aton () e inet_ntoa (), che in sostanza equivale a conversione di un indirizzo IP da e per un intero senza segno

# 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;

Stabilire determinate soglie di ritardo sulla base del il grado numero di accessi falliti in un determinato periodo di tempo (15 minuti in questo esempio). Si dovrebbe basare questo su dati statistici tirato dal vostro tavolo failed_logins come sarà cambiano nel tempo in base al numero di utenti e quanti di loro possono ricordare (e tipo) la loro password.


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

Interrogazione tabella in ogni tentativo di accesso non è riuscito a trovare il numero di accessi non riusciti per un determinato periodo di tempo, diciamo 15 minuti:


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

Se il numero di tentativi per un determinato periodo di tempo è superato il limite, sia far rispettare la limitazione o la forza tutti di usare un captcha (cioè reCaptcha) fino a quando il numero di tentativi falliti oltre il periodo di tempo determinato utente è inferiore alla soglia .

// 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;
            }
        }        
    }
}

Utilizzando reCaptcha ad una certa soglia dovrebbe garantire che un attacco da più fronti si sarebbe fermato e normali utenti del sito non sarebbe verificarsi un ritardo significativo per legittimi tentativi di accesso falliti.

Altri suggerimenti

Ci sono tre approcci di base:. Negozio di informazioni di sessione, memorizzano le informazioni di cookie o memorizzare informazioni IP

Se si utilizza le informazioni di sessione l'utente finale (attaccante) potrebbe forzatamente invocare nuove sessioni, bypassare la tua tattica, e poi di nuovo il login senza alcun ritardo. Le sessioni sono abbastanza semplici da implementare, è sufficiente memorizzare l'ultima nota di tempo di accesso dell'utente in una variabile di sessione, corrispondono contro il tempo corrente, e assicurarsi che il ritardo è stato abbastanza a lungo.

Se si utilizza i cookie, l'utente malintenzionato può semplicemente rifiutare i cookie, tutto sommato, questo non è davvero una cosa fattibile.

Se si traccia gli indirizzi IP è necessario memorizzare i tentativi di accesso da un indirizzo IP in qualche modo, di preferenza in un database. Quando un utente tenta di accedere, è sufficiente aggiornare l'elenco di indirizzi IP registrati. Si dovrebbe eliminare questa tabella a un intervallo ragionevole, indirizzi IP che non sono state attive in un certo tempo dumping. L'insidia (c'è sempre una trappola), è che alcuni utenti potrebbero finire per condividere un indirizzo IP, e nelle condizioni al contorno tuoi ritardi possono influenzare gli utenti inavvertitamente. Dal momento che si sta monitorando accessi non riusciti, e gli accessi solo non è riuscito, questo non dovrebbe causare troppo dolore.

Le esigenze di processo login ridurre la sua velocità per l'accesso sia successo e insuccesso. Il tentativo di accesso per sé non dovrebbe mai essere più veloce di circa 1 secondo. Se lo è, la forza bruta utilizza il ritardo di sapere che il tentativo è fallito perché il successo è più corto di fallimento. Poi, più combinazioni possono essere valutati per secondo.

Il numero di tentativi di accesso simultanee per macchina deve essere limitata dal bilanciamento del carico. Infine, non vi resta che tenere traccia se lo stesso utente o la password viene ri-utilizzati da più di un utente / password tentativo di accesso. Gli esseri umani non possono digitare più velocemente di circa 200 parole al minite. Così, login successiva o simultanea tenta più veloce di 200 parole al minite sono da un insieme di macchine. Questi possono quindi essere reindirizzati a una lista nera in modo sicuro in quanto non è il vostro cliente. Lista nera volte per host non hanno bisogno di essere superiore a circa 1 secondo. Questo non sarà mai inconveniente un essere umano, ma gioca il caos con un tentativo di forza bruta sia in parallelo o in serie.

2 * 10 ^ 19 combinazioni in una combinazione al secondo, eseguito in parallelo su 4 miliardi di indirizzi IP separati, avrà 158 anni per esaurire come spazio di ricerca. Per la durata di un giorno per utente contro 4 miliardi di attaccanti, è necessaria una password alfanumerica completamente casuale lungo 9 posti al minimo. Prendere in considerazione la formazione di utenti in passphrase di almeno 13 posti, 1,7 * 10 ^ 20 combinazioni.

Questo ritardo, motiverà l'attaccante per rubare il file hash della password, piuttosto che la forza bruta tuo sito. Usa approvato, di nome, tecniche di hashing. Vietare l'intera popolazione di Internet IP per un secondo, limiterà l'effetto di attacchi paralleli senza dealy un essere umano possa apprezzare. Infine, se il sistema permette più di 1000 tentativi falliti di accesso in un secondo senza una qualche risposta al bando di sistemi, quindi i vostri piani di sicurezza hanno grossi problemi su cui lavorare. Fissare che risposta automatica prima di tutto.

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.

o, come suggerito da Cyro:

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

E 'un po' ruvido, ma i componenti di base ci sono. Se si aggiorna la pagina, ogni volta che si aggiorna il ritardo otterrà più.

Si potrebbe anche tenere i conteggi in un database, in cui si controlla il numero di tentativi falliti da IP. Tramite esso sulla base di IP e mantenendo i dati al vostro fianco, si impedisce all'utente di essere in grado di cancellare i cookie per fermare il ritardo.

In sostanza, il codice inizio potrebbe essere:

$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;
    }
}

Non riesci tentativi nel database da IP. (Dal momento che si dispone di un sistema di login, suppongo si sa bene come farlo.)

Ovviamente, le sessioni è un metodo allettante, ma qualcuno veramente dedicato possono facilmente rendersi conto che si può semplicemente cancellare il cookie di sessione sui tentativi falliti al fine di eludere l'acceleratore completamente.

Il tentativo di login, recuperano quanti recente (ad esempio, negli ultimi 15 minuti) login tentativi ci sono stati, e il tempo del l'ultimo tentativo.

$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!";
}

È possibile utilizzare le sessioni. Ogni volta che l'utente non riesce un login, si aumenta il valore memorizzare il numero di tentativi. Si può capire il ritardo richiesto dal numero di tentativi, oppure è possibile impostare il tempo effettivo che l'utente è autorizzato a provare di nuovo nella sessione pure.

Un metodo più affidabile potrebbe essere quella di memorizzare i tentativi e-provare-tempo nuovo nel database per quel particolare ipaddress.

IMHO, difesa contro gli attacchi DOS è trattata meglio a livello di server web (o forse anche in hardware di rete), non nel vostro codice PHP.

Io in genere creare la storia di login e le tabelle tentativo di accesso. La tabella tentativo sarebbe il login nome utente, password, indirizzo IP, ecc query sulla tabella per vedere se è necessario ritardare. Consiglierei bloccare completamente per tentativi superiore 20 in un dato tempo (un'ora per esempio).

Come da discussione di cui sopra, le sessioni, i cookie e gli indirizzi IP non sono efficaci - tutti possono essere manipolati da un aggressore

.

Se si vuole prevenire attacchi di forza bruta, allora l'unica soluzione pratica è quello di basare il numero di tentativi sul nome utente fornito, tuttavia notare che questo permette al malintenzionato di DOS sito bloccando utenti validi di eseguire l'accesso.

per es.

$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
 }

Si noti che come scritto, questa procedura perdite informazioni di sicurezza - ossia sarà possibile per qualcuno attacca il sistema per vedere quando un utente accede (il tempo di risposta per il tentativo attaccanti scenderà a 0). Si potrebbe anche mettere a punto l'algoritmo in modo che il ritardo è calcolato sulla base del ritardo precedente e il timestamp sul file.

HTH

C.

I cookie o metodi basati su sessioni sono ovviamente inutili in questo caso. L'applicazione deve controllare l'indirizzo IP o il timestamp (o entrambi) di precedenti tentativi di accesso.

Un controllo IP può essere aggirato se l'attaccante ha più di un IP per iniziare la sua / sue richieste da e può essere fastidioso se più utenti di connettersi al server dallo stesso IP. In quest'ultimo caso, in mancanza di qualcuno login per diverse volte impedirebbe tutti coloro che condividono lo stesso IP dal accedendo con il nome utente che per un certo periodo di tempo.

Un controllo timestamp ha lo stesso problema di cui sopra: ognuno può impedire che chiunque altro dalla registrazione in un particolare account semplicemente provando più volte. Utilizzando un captcha, invece di una lunga attesa per l'ultimo tentativo è probabilmente una buona soluzione.

Le uniche cose in più il sistema di login dovrebbe prevenire condizioni di gara sono sul tentativo funzione di controllo. Ad esempio, nel seguente pseudocodice

$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);
}

Che cosa succede se un utente malintenzionato invia simultanee richieste alla pagina di login? Probabilmente tutte le richieste sarebbero eseguiti con la stessa priorità, e le probabilità sono che nessuna richiesta arriva al istruzioni increment_attempt_number prima che gli altri sono oltre la linea 2. Così ogni richiesta ottiene lo stesso tempo $ e $ tenta il valore e viene eseguito. Prevenire questo tipo di problemi di sicurezza può essere difficile per le applicazioni complesse e coinvolge bloccare e sbloccare alcune tabelle / righe del database, ovviamente rallentare l'applicazione verso il basso.

La risposta breve è: Non fare questo. Non sarà proteggersi da bruta forzatura, si potrebbe anche peggiorare la situazione.

Nessuna delle soluzioni proposte avrebbe funzionato. Se si utilizza l'IP come un qualsiasi parametro per strozzamento, l'attaccante sarà solo coprire l'attacco attraverso un gran numero di indirizzi IP. Se si utilizza la sessione (cookie), l'attaccante sarà solo far cadere i cookie. La somma di tutto quello che viene in mente è, che non c'è assolutamente nulla di una forzatura attaccante bruta non poteva superare.

C'è una cosa, però - basta fare affidamento sul nome utente che ha cercato di accedere Quindi, senza guardare tutti gli altri parametri di monitorare la frequenza con cui un utente ha tentato di accedere e della valvola a farfalla.. Ma un attaccante vuole farti del male. Se riconosce questo, sarà solo anche bruta nomi utente vigore.

Questo si tradurrà in quasi tutti gli utenti di essere strozzata al valore massimo quando tentano di accedere. Il tuo sito web sarà inutile. Attaccante:. Successo

Si potrebbe ritardare la verifica della password, in generale, per circa 200 ms - l'utente del sito sarà quasi non accorgersi che. Ma un bruto-forcer farà. (Anche in questo caso potrebbe estendersi su IP) Tuttavia, niente di tutto questo vi proteggerà da bruta forzare o DDoS -., Come si può non programatically

L'unico modo per farlo è usare l'infrastruttura.

Si dovrebbe usare bcrypt invece di MD5 o SHA-x per hash password, questo renderà decifrare le password molto più difficile se qualcuno ruba il database (perché immagino che siete su un host condiviso o gestito)

Ci scusiamo per deludere, ma tutte le soluzioni qui hanno un debole e non c'è modo di superare al loro interno la logica di back-end.

cballuo fornito una risposta eccellente. Volevo solo restituire il favore, fornendo una versione aggiornata che supporta mysqli. Ho un po 'cambiato le colonne della tabella / campo nelle sqls e altre piccole cose, ma dovrebbe aiutare tutti coloro che cercano per l'equivalente mysqli.

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;
        }
     }        
  }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top