La risposta di Polynomial è azzeccata. Volevo segnalare altre potenziali vulnerabilità: la precedenza degli operatori, la facilità di revisione e la facilità di test.
Qualcosa della forma foo AND bar == baz
è vulnerabile a ottenere la precedenza sbagliata. Vorrei verificare che, in sede di revisione, vi sia una lunga storia di misure di sicurezza sventate a causa di condizioni e priorità composte.
Ora, non c'è niente di sbagliato con quello che hai scritto, ma è molto facile sbagliarlo. Nella maggior parte dei linguaggi va bene perché gli operatori di confronto come ==
hanno una precedenza più alta rispetto agli operatori logici come AND
, quindi la condizione si legge come (notare l'esplicito parens):
isset($_POST['pass_word']) AND
($_POST['pass_word'] == $passwd)
... ma è molto facile sbagliarlo. Ad esempio, AND
e &&
non hanno la stessa precedenza. Cosa succede se invece di usare isset
tu (o qualcuno che ti segue) hai usato una scorciatoia logica-o per impostare un valore predefinito? (Questo non è equivalente a isset
, è solo un esempio di precedenza che causa problemi di sicurezza)
$_POST['pass_word'] || '' == $passwd
Questa linea gotcha è in realtà questa:
$_POST['pass_word'] || ('' == $passwd)
Ora chiunque abbia una password vuota, forse da un errore che lo recupera, effettuerà sempre il login.
Quando si tratta di codice sicuro, rendi esplicito il tuo paren per rendere noto il tuo intento al revisore e al programma.
Ancora meglio, evita le condizioni composte interamente nel codice di sicurezza. Non puoi sbagliare usando quello che non hai usato. Ad esempio, questo codice sfrutta l'incapsulamento e ritorno anticipato per fornire metodi di verifica e test molto facili.
// This deals with input and normalizing it.
function can_login($user) {
if( !isset($_POST['pass_word']) ) {
// Or it could throw an exception to give the
// caller more information about why they didn't login
return false;
}
return check_password($user, $_POST['pass_word']);
}
// This only deals with checking a password.
// Don't use this, it still has all the flaws Polynomial
// pointed out.
function check_password($user, $check) {
// get_password() should throw an exception if it cannot
// find that user's password to avoid accidentally thinking
// their password is false or 0 or '' or something. This
// avoids relying on the caller doing the check for failure.
return $check === get_password($user);
}
Potrebbe non essere il metodo migliore, ma è piccolo e facile da testare e recensire. Questo separa i dettagli della registrazione di un utente in (che potrebbe essere più di un controllo della password, come se fossero un utente valido) dal controllo della propria password. Qualsiasi problema può essere individuato e risolto senza dover guadare il resto del codice di accesso. Un revisore controllerà get_password
e tutti i luoghi check_password
e can_login
vengono utilizzati.
Non posso sottolineare abbastanza quanto sia importante isolare i componenti protetti nel più piccolo pezzo possibile, usando il più semplice codice possibile, per renderli facili da recensire e testare.
BONUS : ecco qualcosa che ho pensato di fare, ma ho deciso che renderebbe il codice meno sicuro.
La mia idea originale era di avere una funzione che restituiva più valori: non fornivano una password, la loro password era sbagliata, la loro password era giusta. Ciò consentirebbe al chiamante di conoscere la differenza tra "la password è sbagliata" e "non hanno fornito una password, qualcosa è sbagliato".
function can_login($user) {
if( !isset($_POST['pass_word']) ) {
return NO_PASSWORD;
}
$password = get_password($user);
if( $_POST['pass_word'] === $password ) {
return YES;
}
else {
return NO;
}
}
E poi lo chiami così:
$rslt = can_login($user);
if( $rslt == YES ) {
echo "Login!\n";
}
else if( $rslt == NO ) {
echo "Wrong password.\n";
}
else if( $rslt == NO_PASSWORD ) {
echo "No password given.\n";
}
Il problema è che rende la chiamata alla routine correttamente più complicata. Peggio ancora, se non leggi i documenti, penseresti che fosse questo:
if( can_login($user) ) {
echo "Yes!\n";
}
else {
echo "No!\n";
}
Poiché can_login
restituisce sempre un valore vero, ora chiunque può accedere.
Ritorna a casa il punto: rendi il tuo codice sicuro il più semplice e infallibile possibile per il revisore, per il tester e per il chiamante. In questo caso, le eccezioni sono la giusta strada da percorrere in quanto forniscono un canale per ottenere più informazioni mantenendo can_login
un semplice booleano. Se dimentichi di controllare che il programma si arresti in modo anomalo, ma l'accesso non riesce.