site1.com
site2.com
site2.com
, há uma requisição POST site1.com/transferir?conta=1234&valor=1000
Mas por que eu preciso disso na tela de login?
Para garantir que um atacante não faça um usuário passar-se por ele
Referer
(ou Origin
) da requisição para garantir que a origem é do seu siteSession ID
do usuário e um timestamp, por exemploSaudades, RapidShare
use Sonata\GoogleAuthenticator\GoogleAuthenticator;
use Sonata\GoogleAuthenticator\GoogleQrUrl;
/* class { ... */
public function __construct() {
$this->authenticator = new GoogleAuthenticator();
$this->secret = getSecretFromSomewhere();
}
public function check(string $code) : bool {
return $this->authenticator->checkCode(
$this->secret,
$code
);
}
public fuction generateSecret() : string {
return $this->authenticator->generateSecret();
}
public fuction generateSecret(string $secret) : string {
return GoogleQrUrl::generate('My App', $secret);
}
1000000 / 86400 = 11.574074074
public function login(string $username, string $password) : bool
{
$user = $this->findByUsername($username);
if (! $user) {
return false;
}
return $this->verifyPassword(
$password,
$user->getPassword()
);
}
É possível saber que usuários existem em sua aplicação através de timing attack
Modifique o código para que a custosa função verifyPassword
seja sempre invocada!
public function login(string $username, string $password) : bool
{
$storedPassword = $this->generateFakePassword();
$user = $this->findByUsername($username);
if ($user) {
$storedPassword = $user->getPassword();
}
return ($this->verifyPassword($password, $storedPassword))
&& ($user !== null);
}
Será que não estamos exagerando?
É realmente possível ver essa diferença?
Vamos testar, então!
Executando os scripts login-user-enumeration.php
e login-user-enumeration-fixed.php
1. Prefixe o token com um identificador (NÃO use diretamente o ID do usuário)
$identifier = hash('sha256', $userId); // 64 bytes
$token = bin2hex(random_bytes(32)); // 64 bytes
$cookie = $identifier . $token;
2. Crie uma tabela no banco de dados com o identificador acima e somente o hash do token ao invés dele
CREATE TABLE `rememberme_tokens` (
`id` int unsigned AUTO_INCREMENT,
`identifier` char(64) NOT NULL,
`token_hash` char(128) NOT NULL,
PRIMARY KEY (`id`)
)
$hashedToken = hash('sha512', $token);
$pdo->prepare('INSERT INTO rememberme_tokens (NULL, ?, ?)')
->execute([$identifier, $hashedToken]);
3. Ao consultar o banco, utilize apenas o identificador
$query = $pdo->prepare(
'SELECT `token_hash` FROM `rememberme_tokens`
WHERE `identifier` = ? LIMIT 1'
);
$hashFromDatabase = $query->fetch(PDO::FETCH_COLUMN);
4. Faça o hash do token informado no cookie do usuário e compare-o com o que está no banco (com hash_equals()
)
$hashedToken = hash('sha512', substr($cookie, 64));
if (hash_equals($hashedToken, $hashFromDatabase)) {
redirectToHome();
} else {
redirectToLogin();
}
algorithm=none