Última modificação em 10 de janeiro de 2021.
O ataque Cross-Site Request Forgery (CSRF) é um abuso de segurança comum que ocorre em toda a web. Proteger o servidor contra esse ataque é um mecanismo de proteção de primeiro nível para proteger o seu site.
Usuários mal-intencionados da Internet costumavam clonar solicitações para atacar servidores vulneráveis. Essa clonagem pode acontecer incorporando o link do site malicioso à página da web do usuário.
A implementação de anti-CSRF reduz a vulnerabilidade do site. Com essa proteção, o site rejeita o acesso malicioso que envia solicitações sem token CSRF ou errado.
O diagrama a seguir mostra a validação da solicitação do usuário contra o ataque CSRF. Se um usuário genuíno postar o formulário com o token apropriado, o servidor processa a solicitação. Caso contrário, rejeita, na ausência do parâmetro de token CSRF.
Veremos um exemplo de código formulário de contato em PHP com proteção CSRF neste tutorial. Com essa proteção, ele garante a autenticidade da solicitação antes de processá-la.
Criei um serviço em PHP para lidar com a validação de segurança contra o ataque CSRF. O servidor rejeitará as solicitações dos usuários que não tenham token ou token inválido.
Se você deseja ter um formulário de contato com proteção CSRF e mais recursos de segurança, obtenha o Iris .
O que há dentro?
- Sobre este exemplo
- Gerar token CSRF e criar sessão PHP
- Renderizar o formulário de contato com o token CSRF
- Validação de Anti Cross-Site Request Forgery (CSRF) em PHP
- Serviço de segurança para gerar, inserir e validar o token CSRF
- Resultado: resposta de validação CSRF do servidor
Sobre este exemplo
Este código implementa a proteção Anti CSRF em um formulário de contato do PHP . Ele processa um formulário de contato. Os gerenciadores de postagem deste formulário validam as solicitações do usuário contra o ataque CSRF.
Ao carregar a página de destino, o script PHP gera o token CSRF. O rodapé do formulário terá este token como um campo oculto. Além disso, ele gerencia o token em uma sessão PHP.
Ao postar os campos do formulário, o código PHP verificará o parâmetro do token CSRF. Se encontrado, ele valida o token da sessão.
Se o usuário enviar uma solicitação sem um token CSRF, o servidor rejeitará a solicitação. Além disso, se o token não corresponder ao token da sessão, o servidor rejeitará a solicitação.
Com a validação do token CSRF bem-sucedida, ele enviará o e-mail de contato para o endereço de destino. O diagrama a seguir mostra a estrutura do arquivo deste exemplo.
Gerar token CSRF e criar sessão PHP
Em uma página inicial, o script de rodapé do formulário invoca SecurityService. Esta é uma classe PHP para gerar um token CSRF.
Ele grava o token em uma sessão PHP para referência futura. Isso ajudará na hora de processar a validação do CSRF após a postagem do formulário.
O rodapé do formulário é um arquivo de estrutura que carrega o token gerado em um campo oculto.
O snippet de código abaixo é do SecurityService.php para gerar o toke CSRF. O código completo da classe de serviço é mostrado na próxima seção deste artigo.
SecurityService.php (código para gerar token CSRF)
/** * Gerar, armazenar e retornar o token CSRF * * @return string [] */ public function getCSRFToken () { if (vazio ($ this-> sessão [$ this-> sessionTokenLabel])) { $ this-> sessão [$ this-> sessionTokenLabel]=bin2hex (openssl_random_pseudo_bytes (32)); } if ($ this-> hmac_ip!==false) { $ token=$ this-> hMacWithIp ($ this-> sessão [$ this-> sessionTokenLabel]); } outro { $ token=$ this-> sessão [$ this-> sessionTokenLabel]; } return $ token; }
Este é um formulário de contato HTML com os campos usuais nome, e-mail, assunto e mensagem. Além disso, há um campo oculto csrf-token com o token gerado.
A ação de envio processa validação do formulário jQuery antes de postar os parâmetros no PHP.
O script de validação do lado do cliente lida com a validação básica no envio. Ele aplica a verificação de não vazio em cada campo.
index.php (modelo HTML)
Proteção contra CSRF usando PHP Proteção contra CSRF usando PHP
php if (! empty ($ message)) {?>php if (isset ($ message)) {?> php echo $ message; }}?>
Este é o script de rodapé do formulário que aciona o manipulador de serviço para gerar tokens. O insertHiddenToken () grava o código HTML para carregar o campo do token csrf no formulário.
view/framework/form-footer.php
php require_once __DIR__.'/../../lib/SecurityService.php'; $ antiCSRF=new \ Phppot \ SecurityService \ securityService (); $ antiCSRF-> insertHiddenToken ();
assets/js/validation.js
function validateContactForm () { var válido=verdadeiro; $ ("# userName"). removeClass ("campo de erro"); $ ("# userEmail"). removeClass ("error-field"); $ ("# assunto"). removeClass ("campo de erro"); $ ("# content"). removeClass ("campo de erro"); $ ("# userName-info"). html (""). hide (); $ ("# userEmail-info"). html (""). hide (); $ ("# subject-info"). html (""). hide (); $ ("# content-info"). html (""). hide (); $ (". mensagem de validação"). html (""); $ (". phppot-input"). css ('border','# e0dfdf 1px solid'); var userName=$ ("# userName"). val (); var userEmail=$ ("# userEmail"). val (); var subject=$ ("# assunto"). val (); var content=$ ("# content"). val (); if (userName.trim ()=="") { $ ("# userName-info"). html ("obrigatório."). css ("color","# ee0000"). show (); $ ("# userName"). css ('border','# e66262 1px solid'); $ ("# userName"). addClass ("campo de erro"); válido=falso; } if (userEmail.trim ()=="") { $ ("# userEmail-info"). html ("obrigatório."). css ("color","# ee0000"). show (); $ ("# userEmail"). css ('border','# e66262 1px solid'); $ ("# userEmail"). addClass ("error-field"); válido=falso; } if (! userEmail.match (/^ ([\ w-\.] + @ ([\ w-] + \.) + [\ w-] {2,4})? $/)) { $ ("# userEmail-info"). html ("endereço de e-mail inválido."). css ("color", "# ee0000"). show (); $ ("# userEmail"). css ('border','# e66262 1px solid'); $ ("# userEmail"). addClass ("error-field"); válido=falso; } if (assunto=="") { $ ("# subject-info"). html ("obrigatório."). css ("color","# ee0000"). show (); $ ("# assunto"). css ('borda','# e66262 1px sólido'); $ ("# assunto"). addClass ("campo de erro"); válido=falso; } if (content=="") { $ ("# userMessage-info"). html ("obrigatório."). css ("color","# ee0000"). show (); $ ("# content"). css ('border','# e66262 1px solid'); $ ("# content"). addClass ("campo de erro"); válido=falso; } if (válido==falso) { $ ('. campo de erro'). first (). focus (); válido=falso; } retorno válido; }
Validação Anti Cross-Site Request Forgery (CSRF) em PHP
Ao enviar o formulário de contato incorporado ao token, a ação do formulário executa o seguinte script.
A função validate () do SecuritySercive compara o token postado com aquele armazenado na sessão.
Se uma correspondência for encontrada, ele continuará enviando o e-mail de contato. Caso contrário, ele reconhecerá o usuário com uma mensagem de erro.
index.php (validação de PHP CSRF e tratamento de formulários)
php use Phppot \ MailService; session_start (); if (! empty ($ _ POST ['enviar'])) { require_once __DIR__.'/lib/SecurityService.php'; $ antiCSRF=new \ Phppot \ SecurityService \ securityService (); $ csrfResponse=$ antiCSRF-> validate (); if (! empty ($ csrfResponse)) { require_once __DIR__.'/lib/MailService.php'; $ mailService=new MailService (); $ response=$ mailService-> sendContactMail ($ _ POST); if (! vazio ($ resposta)) { $ message="Olá, recebemos sua mensagem. Obrigado."; $ type="sucesso"; } outro { $ message="Incapaz de enviar email."; $ type="erro"; } } outro { $ message="Alerta de segurança: Incapaz de processar o seu pedido."; $ type="erro"; } } ?>
Serviço de segurança para gerar, inserir, validar token CSRF
Esta classe de serviço criada em PHP inclui métodos para processar as operações relacionadas à proteção CSRF.
Ele define uma propriedade de classe para definir o nome do campo do token do formulário e o índice da sessão.
Possui métodos para gerar tokens e gravá-los no HTML e em uma sessão de PHP.
Ele usa mitigações XSS ao escrever o rodapé do formulário com o token.
Além disso, tem a opção de excluir alguns URLs do processo de validação. Os URLs excluídos ignoram o processo de validação CSRF.
O código obtém o URL da solicitação atual das variáveis PHP SERVER. Em seguida, ele o compara com a matriz de URLs excluídos para pular a validação.
lib/SecurityService.php
php /** * Copyright (C) Phppot * * Distribuído sob'The MIT License (MIT)' * Em essência, você pode fazer uso comercial, modificar, distribuir e uso privado. * Embora não seja obrigatório, você deve atribuir a URL Phppot em seu código ou site. */ namespace Phppot \ SecurityService; /** * Classe de biblioteca usada para proteção contra CSRF. * CSRF é a abreviatura de Cross Site Request Forgery. * Cross-Site Request Forgery (CSRF) é um ataque que força um usuário final a executar ações indesejadas em um * aplicativo da web no qual eles estão autenticados no momento. Defn. por OWASP. * * O token baseado na sessão do usuário é gerado e codificado com seu endereço IP. * Existem tipos de operações com as quais o DDL é executado. * Envia usando o formulário HTML geral e envia usando AJAX. * Estamos inserindo um token CSRF dentro do formulário e ele é validado com o token presente na sessão. * Isso garante que os ataques CSRF sejam evitados. * * Se você estiver personalizando o aplicativo e criando um novo formulário, * você deve garantir que a prevenção de CSRF esteja em vigor. form-footer.php * é o arquivo que deve ser incluído onde o token deve ser ecoado. * Após o eco, a validação do token acontece no controlador e é * o ponto de entrada comum para todas as chamadas. Portanto, não há necessidade de fazer nenhum código separado para * Validação CSRF com relação a cada funcionalidade. * * O token CSRF é escrito como um tipo de entrada oculto dentro da tag de formulário html com um rótulo $ formTokenLabel. * * @author Vincy * @ versão 3.5-Rastreamento de endereço IP removido, pois é bom para conformidade com o GDPR. * */ classe securityService { private $ formTokenLabel='eg-csrf-token-label'; private $ sessionTokenLabel='EG_CSRF_TOKEN_SESS_IDX'; privado $ post=[]; $ sessão privada=[]; $ servidor privado=[]; private $ excludeUrl=[]; private $ hashAlgo='sha256'; private $ hmac_ip=true; private $ hmacData='ABCeNBHVe3kmAqvU2s7yyuJSF2gpxKLC'; /** * NULL não é um tipo de array válido * * @param array $ post * @param array $ session * @param array $ server * @throws \ Error */ função pública __construct ($ excludeUrl=null, & $ post=null, & $ session=null, & $ server=null) { if (! \ is_null ($ excludeUrl)) { $ this-> excludeUrl=$ excludeUrl; } if (! \ is_null ($ post)) { $ this-> post=& $ post; } outro { $ this-> post=& $ _POST; } if (! \ is_null ($ server)) { $ this-> server=& $ server; } outro { $ this-> server=& $ _SERVER; } if (! \ is_null ($ sessão)) { $ this-> sessão=& $ sessão; } elseif (! \ is_null ($ _ SESSION) && isset ($ _ SESSION)) { $ this-> sessão=& $ _SESSION; } outro { lançar new \ Error ('Nenhuma sessão disponível para persistência'); } } /** * Insira um token CSRF em um formulário * * @param string $ lockTo * Este token CSRF é válido apenas para este endpoint de solicitação HTTP * @param bool $ echo * se verdadeiro, echo em vez de retornar * @return string */ public function insertHiddenToken () { $ csrfToken=$ this-> getCSRFToken (); echo" xssafe ($ this-> formTokenLabel)."\""."value=\"". $ this-> xssafe ($ csrfToken)."\""."/>"; } //xss funções de mitigação função pública xssafe ($ data, $ encoding='UTF-8') { return htmlspecialchars ($ data, ENT_QUOTES | ENT_HTML401, $ encoding); } /** * Gerar, armazenar e retornar o token CSRF * * @return string [] */ public function getCSRFToken () { if (vazio ($ this-> sessão [$ this-> sessionTokenLabel])) { $ this-> sessão [$ this-> sessionTokenLabel]=bin2hex (openssl_random_pseudo_bytes (32)); } if ($ this-> hmac_ip!==false) { $ token=$ this-> hMacWithIp ($ this-> sessão [$ this-> sessionTokenLabel]); } outro { $ token=$ this-> sessão [$ this-> sessionTokenLabel]; } return $ token; } /** * hashing com endereço IP removido para facilitar a conformidade com o GDPR * e hmacdata é usado. * * @param string $ token * @return string dados hash */ função privada hMacWithIp ($ token) { $ hashHmac=\ hash_hmac ($ this-> hashAlgo, $ this-> hmacData, $ token); return $ hashHmac; } /** * retorna o URL da solicitação atual * * @return string */ função privada getCurrentRequestUrl () { $ protocolo="http"; if (isset ($ this-> servidor ['HTTPS'])) { $ protocolo="https"; } $ currentUrl=$ protocolo."://". $ this-> servidor ['HTTP_HOST']. $ this-> servidor ['REQUEST_URI']; return $ currentUrl; } /** * função principal que valida a tentativa de CSRF. * * @throws \ Exception */ public function validate () { $ currentUrl=$ this-> getCurrentRequestUrl (); if (! in_array ($ currentUrl, $ this-> excludeUrl)) { if (! empty ($ this-> post)) { $ isAntiCSRF=$ this-> validateRequest (); if (! $ isAntiCSRF) { //tentativa de ataque CSRF //tentativa de CSRF é detectada. Não precisa revelar essa informação //para o invasor, então falhando sem informações. //Código de erro 1837 significa tentativa CSRF e isso é para //nossos objetivos de identificação. retorna falso; } return true; } } } /** * a validação real do CSRF acontece aqui e retorna booleano * * @return boolean */ public function isValidRequest () { $ isValid=false; $ currentUrl=$ this-> getCurrentRequestUrl (); if (! in_array ($ currentUrl, $ this-> excludeUrl)) { if (! empty ($ this-> post)) { $ isValid=$ this-> validateRequest (); } } return $ isValid; } /** * Valide uma solicitação com base na sessão * * @return bool */ public function validateRequest () { if (! isset ($ this-> sessão [$ this-> sessionTokenLabel])) { //Token CSRF não encontrado retorna falso; } if (! empty ($ this-> post [$ this-> formTokenLabel])) { //Vamos extrair os dados POST $ token=$ this-> post [$ this-> formTokenLabel]; } outro { retorna falso; } if (! \ is_string ($ token)) { retorna falso; } //Pegue o token armazenado if ($ this-> hmac_ip!==false) { $ esperado=$ this-> hMacWithIp ($ this-> sessão [$ this-> sessionTokenLabel]); } outro { $ esperado=$ this-> sessão [$ this-> sessionTokenLabel]; } return \ hash_equals ($ token, $ esperado); } /** * remove o token da sessão */ public function unsetToken () { if (! empty ($ this-> sessão [$ this-> sessionTokenLabel])) { não definido ($ this-> sessão [$ this-> sessionTokenLabel]); } } }
Este MailService.php usa a função mail () do núcleo do PHP para enviar os e-mails de contato. Você pode substituí-lo pelo SMTP via script de envio de e-mail.
lib/MailService.php
php namespace Phppot; classe MailService { função sendContactMail ($ postValues) { $ name=$ postValues ["userName"]; $ email=$ postValues ["userEmail"]; $ subject=$ postValues ["assunto"]; $ content=$ postValues ["content"]; $ toEmail="ADMIN EMAIL"; $ mailHeaders="De:". $ name."(". $ email.") \ r \ n"; $ response=mail ($ toEmail, $ subject, $ content, $ mailHeaders); return $ response; } }
Resultado: Resposta de validação CSRF do servidor
A imagem mostra o formulário de contato usual abaixo. Já vimos essa saída em muitos tutoriais de formulários de contato antes.
Abaixo da interface do formulário, esta imagem mostra a mensagem de alerta de segurança em vermelho. Ele reconhece os usuários que enviam solicitações com o token errado ou vazio.
Conclusão
Assim, implementamos a proteção anti-CSRF em um formulário de contato do PHP.
Espero que o código de exemplo seja útil e você obtenha o processo de implementação que discutimos aqui.
Criamos uma classe SecurityService em PHP para lidar com a proteção CSRF. É reutilizável para vários aplicativos onde quer que você precise ativar a proteção CSRF.
O código PHP que retorna mensagens de resposta reconhece o usuário adequadamente.