Cuidar da segurança é um dos pilares para a sustentação de qualquer aplicação na web. Sem o mínimo não é possível nem mesmo garantir uma razoável disponibilidade.
Nesse artigo eu listo alguns dos tópicos que acredito serem de fundamental importância para a integridade e confidencialidade nos processo de autenticação (com foco em autenticação por cookie). Nas próximas semanas novos artigos correlatos à segurança serão publicados.
- Use HTTPS em todo lugar;
- Armazene as senhas com segurança;
- Não impeça o seu usuário de criar uma senha forte;
- Não transporte pela rede, sem motivo, os dados sensíveis do seu usuário;
- Limite as tentativas de login;
- Deixe-o opcionalmente resetar a senha;
- Cuide da criação dos Cookies e, após o logout, destrua os identificadores da sessão;
1) Use HTTPS em todo lugar
Há alguns anos era comum os sites apenas forçarem o uso do protocolo HTTPS em áreas críticas e sensíveis como, por exemplo, no checkout, login ou cadastro.
Hoje, não só pela maior acessibilidade em gerar certificados SSL, é uma recomendação e um padrão que os sites rodem completamente sob o protocolo HTTPS. O HTTP caminha para se tornar defasado e os navegadores em breve emitirão warnings (sem exceção) para sites que rodem nele.
Formação Desenvolvedor PHP
Conhecer a formaçãoA Mozilla (mantenedora do Firefox) publicou no final de 2015 um post demonstrando intenção e um projeto para depreciar o HTTP.
Se a sua preocupação é o preço que se paga por um certificado SSL, bom, ela não deve mais existir! Meados de 2016 foi lançado o Let’s Encrypt, uma autoridade de certificação completamente gratuita. Ela é mantida por grandes organizações e através de doações. A ideia é tornar a web completamente HTTPS em alguns anos.
Há de se ressaltar, no entanto, que o protocolo HTTPS em si não garantirá que o seu site seja confiável, ele apenas garantirá a confidencialidade da troca de informações. Se você não cuidar dos dados do seu usuário como, por exemplo, se os trafegar no subterrâneo da web sem segurança, se salvar a senha dele em plain text na base, etc, de nada adiantará.
Ah, falando em protocolo HTTP, muito recomendo o nosso curso de introdução ao http. Com ele você terá uma visão geral muito bacana sobre os “bastidores” de como a web funciona. Essencial para qualquer desenvolvedor:
2) Armazene as senhas com segurança
Nunca armazene as senhas dos usuários em “plain text”, use algum moderno hash de password como o bcrypt (mais acessível) ou o Argon2, vencedor da última competição de password hashing. Ele é o que de mais moderno temos para passwords.
Se sua linguagem suportar nativamente (como é o caso do PHP 7.2) a library Libsodium, que é uma das mais recomendadas entre os especialistas de segurança como uma completa toolkit de criptografia e hashing, por agrupar os melhores e mais sofisticados algoritmos (como o Argon2, anteriormente mencionado), use-a. Mesmo se não suportá-la nativamente (na standard library), certamente haverá uma forma de importá-la para o seu projeto.
Não devemos, como desenvolvedores, partindo do pressuposto de que a maioria de nós não é especialista em criptografia, criar as nossas próprias bibliotecas para resolver tais questões. Usemos as criadas, testadas e auditadas por especialistas. Andar sobre um terreno desconhecido pode nos levar à nefastas consequências.
Vale a pena reforçar: Descarte qualquer artigo ou referência que indique algoritmos de hashing como o MD5 e SHA1 para armazenamento de passwords (mesmo que com salt’s). Eles não foram projetados para esse propósito. Além disso, não são seguros, principalmente por serem suscetíveis à ataques de colisão. Uma colisão num algoritmo de hashing é, basicamente, uma mesma saída sendo representada para diferentes entradas (sendo que a premissa deles é, em teoria, uma representação (de saída) única para cada entrada).
Leitura recomendada: Password Storage Cheat Sheet.
Se você usa um Web Framework (Rails, Laravel etc), é bem possível que ele já abstraia uma camada de autenticação usando as melhores práticas. Mas é sempre bom tirar um tempo e consultar a documentação.
3) Não impeça o seu usuário de criar uma senha forte
É extremamente comum sites que possuem regras bastante estranhas (quiçá equivocadas) para a definição de passwords. Eu passo bastante por esse tipo de situação pois eu uso um password manager e crio senhas únicas de mais de 32 caracteres para cada serviço, no entanto, não é todo site que as aceita.
Algumas das regras que considero equivocadas:
Exigir que o password tenha apenas letras e números (sem caracteres especiais);
Se o seu site usa um hash de password como comentado anteriormente (e ele deveria usar um), ter a senha com caracteres especiais, números, letras etc, nunca vai ser um problema. Muito pelo contrário, se puder, incentive o seu usuário a criar senhas que usem toda essa pluralidade (mas, sem obrigá-lo, ademais, a segurança também é uma responsabilidade que precisa partir do usuário).
Delimitar o tamanho do password;
Alguns sites delimitam um máximo de 14 ~ 16 caracteres para a senha. Isso não faz sentido. Limitar um mínimo, até faz e, nesse caso, é você prezando (ou ao menos tentando) pela qualidade da senha do seu usuário. Por exemplo: “A senha deve ter no mínimo 6 caracteres”.
Se o seu site lida com informações sensíveis e críticas, você até pode ter um sistema que aceite senhas pequenas, desde que você implemente, por exemplo, autenticação multifator (multi-factor authentication). Um exemplo são os bancos, a senha web é pequena, no entanto, não é possível acessar a interface da conta sem confirmar o token que é gerado por aplicativo ou enviado por SMS.
A estratégia e o critério aqui vai depender do nicho do seu negócio, da criticidade das informações transacionadas por ele.
Como regra geral: Se o seu usuário quer cadastrar uma senha forte, não o impeça disso. É frustrante.
4) Não transporte pela rede, sem motivo, os dados sensíveis do seu usuário
Eu já passei, mais de uma vez, pela situação de o site me enviar no e-mail a senha que eu usei no cadastro. Isso é uma das piores coisas que você pode fazer ao confirmar um cadastro.
A senha do seu usuário é um dado sensível, extremamente sensível, é sagrada. Ela não pode e nem deve ser exposta. Tanto que, ao gravá-la na base de dados, temos o cuidado de usar um hash de password.
Infelizmente a maioria dos usuários ainda não tem a cultura de usar um password manager e, naturalmente, com uma web tão saturada, tantos serviços, tantas redes sociais, as senhas são normalmente reutilizadas em dezenas de serviços. É bem que possível que aquela senha que foi enviada no e-mail seja a porta de entrada para dezenas de outros serviços que o seu usuário utiliza.
5) Limite as tentativas de login
Logins são alvos de bots em ataques de força bruta. Criar um mecanismo para limitar um número de vezes que um determinado IP pode tentar logar (ou IP e username) é uma boa alternativa.
Alguns frameworks já possuem tal implementação, bastando apenas utilizá-la, normalmente é um middleware que precisa ser atrelado à rota de login. Cada tentativa de acesso é salva em cache (de preferência cache em memória) e se ultrapassar o limite estabelecido (por exemplo, 6 tentativas), o usuário fica bloqueado de tentar novamente por um ou dois minutos, por exemplo (a depender da sua escolha).
Se o seu login estiver sob forte ameaça desse ataque, uma opção paliativa é usar um captcha como o reCaptcha do Google, ao menos que temporariamente. O problema dessa opção é que prejudica muito a usabilidade do usuário mas, dependendo da situação, pode ser uma grande aliada.
Se o ataque de força bruta estiver generalizado e vindo de muitos diferentes IP’s, talvez uma boa opção seja utilizar um serviço de segurança que trabalhe como um firewall ainda no DNS mitigando-os (como a Cloudflare).
6) Deixe-o opcionalmente resetar a senha
Essa opção é excelente para a usabilidade do usuário, imagina se ele tivesse que entrar em contato sempre que precisasse alterar a senha? Mesmo que essa não seja uma prática rotineira, seria contra-produtivo.
Mas há de se ressaltar que esse é um processo razoavelmente crítico e que costuma sofrer ataques, portanto, precisa ser bem modelado. Abaixo vou listar o que não deve ser feito ao resetar uma senha e algumas recomendações do que pode / deve ser feito.
O que não deve ser feito:
- Não envie a nova senha no e-mail;
- Não envie a senha antiga no e-mail;
Pra começo de conversa, se você ao menos tem o “poder” de conhecer a senha antiga do seu usuário, está errado, pois isso mostra que ela está sendo armazenada em plain text.
O que deve / pode ser feito:
- Dê opção no seu painel administrativo para o usuário desativar a opção de recuperação de senha.
Pode parecer “estranha” essa hipótese, mas quem usa um password manager pode não ter interesse em ter a função de recuperar senha. Pra ele não faz muito sentido. Tê-la sem que ele vá utilizar, pode vir a ser, no futuro, uma brecha para a segurança da conta. Vamos pensar, hipoteticamente: ele esquece a conta de e-mail logada no computador da empresa onde trabalha. Um usuário a acessa e então pode solicitar a recuperação da senha de algum serviço que ele usa, o e-mail chegará, ele alterará a senha e por consequência, terá o controle da conta.
É verdade que a hipótese acima não aconteceria se houvesse uma verificação adicional como, por exemplo, enviar um token via SMS para o usuário confirmar e só então enviar o e-mail com o link para resetar a senha.
O fluxo padrão de uma recuperação de senha efetiva:
- Gere um token para recuperação da senha e salve-o no banco de dados;
- Se uma nova tentativa de recuperar a senha for realizada, remova o token anteriormente criado e gere um novo;
- Envie o token no e-mail do usuário, em um link, para que ele não precise copiá-lo e depois colá-lo em algum input do site para validação;
- Após a definição da nova senha, remova o token da base de dados;
Há diferentes estratégias para essa geração e avaliação dos tokens. Existem detalhes conceituais que precisam ser considerados, como: avalio o token diretamente na base de dados ou avalio em constant time usando uma função segura para avaliação de strings? Em constant time é a melhor opção, pois evita timing attack. No PHP temos a função hash_equals()
que compara duas strings de forma segura.
O objetivo desse artigo não é adentrar em detalhes de implementação, mas eu deixo aqui uma excelente referência para estudo.
Formação Especialista Spring
Conhecer a formação7) Cuide da criação dos Cookies e, após o logout, destrua os identificadores da sessão
Num sistema tradicional de autenticação por cookie, temos essa relação:
A credencial do usuário é validada, um cookie é enviado na resposta da requisição HTTP (e o navegador trata de criá-lo no disco). No lado do servidor, um arquivo que identifica esse Cookie também é criado e é nesse arquivo que alguns dados dos usuários são salvos (id etc), no servidor, isso é o que chamamos de “sessão”. Normalmente é salvo no disco, mas também pode ser salvo em memória (memcached, redis etc). Sempre que o seu usuário fizer logout, essa referência que ficou no servidor (esse arquivo de sessão), precisa ser forçadamente removido.
Segurança:
Sempre crie os seus Cookies configurando-os como “Secure” e “httpOnly”.
Quando criado como “Secure”, ele só roda sob HTTPS. E quando criado como “httpOnly” não é possível resgatá-lo via JavaScript, isso previne que algum ataque XSS bem sucedido roube-o.
Leitura recomendada: httpOnly
Ah, claro, outra boa prática: criptografe o conteúdo dos seus cookies. Se você observar novamente a última imagem, mais especificamente na key “content”, verá que é um conteúdo criptografado. A ideia é não expor facilmente alguma informação sensível.
Novamente, vale reiterar: se você usa um framework importante da sua linguagem, teoricamente ele segue (ou deveria) as melhores práticas acima mencionadas. Mas é bom certificar-se de tais pormenores.
Previna ataques cross-site utilizando SameSite cookies:
SameSite é um atributo relativamente novo disponível para a criação de Cookies. Ele foi colocado como recomendação da IETF no ano passado (em 2016).
Esse atributo é suportado pelo Chrome desde a versão 59. O suporte completo você pode conferir aqui. Ele resolve a maioria dos ataques de CSRF.
Um ótimo case é do Dropbox, a equipe de engenharia deles explica aqui como aplicarem esse novo atributo e o que ele resolve para eles.
Aproveite e também leia: Cross-Site Request Forgery (CSRF) e abordagens para mitigá-lo
Concluindo
Há, certamente, outros tópicos importantes intrinsecamente ou não relacionados à autenticação e que certamente abordarei em outros artigos como, por exemplo, cuidar da validação dos dados recebidos, na camada de acesso ao banco de dados usar queries parametrizadas e assim por diante.
Até a próxima!