Validação de Email em PHP
Este script é suficiente para validar a entrada de e-mail do usuário?
<?php
//1 DATABASE CONNECTION
$dbHost = "HOST"; $dbUser = "USER";
$dbPassword = "PASSWORD"; $dbName = "DATABASE";
try {
$dsn = "mysql:host=" . $dbHost . ";dbname=" . $dbName; $pdo = new PDO($dsn, $dbUser, $dbPassword); $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch(PDOException $e) {
echo "DB Connection Failed: " . $e->getMessage(); exit(0); } //1 END //2 ADD EMAIL TO DATABASE //set date and time date_default_timezone_set('America/Los_Angeles'); $timestamp = strtotime('NOW');
$dateTime = date('Ymd-His', $timestamp);
//variable to store ipv4 address
$userIP4 = gethostbyname($_SERVER['REMOTE_ADDR']);
//storing ip6 could be something like: "bin2hex(inet_pton($_SERVER['REMOTE_ADDR']));" but I couldn't figure out if the output was correct, because it looked nothing like an ipv6 address..... if(filter_var($userIP4, FILTER_VALIDATE_IP)) {
//yes it's valid IPv4
if($_SERVER['REQUEST_METHOD'] == 'POST') { $email = htmlspecialchars($_POST['email']); //convert special characters to HTML entities (&,",<,>) $Temail = trim($email); //trim spaces on ends //allow international characters if(preg_match("/^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$^/", $Temail)) { //prevents invalid email addresses header("Location: invalid.html"); exit (0); } else { //Check Email Domain MX Record $email_host = strtolower(substr(strrchr($Temail, "@"), 1)); if (!checkdnsrr($email_host, "MX")) {
header("Location: invalid.html");
exit (0);
} else {
//Prevent users from inputting a specific domain...
$notallowed = [ 'mydomain.com', ]; $parts = explode('@', $Temail); //Separate string by @ characters (there should be only one) $domain = array_pop($parts); //Remove and return the last part, which should be the domain if ( ! in_array($domain, $notallowed)) { //checks database to make sure the email is not a duplicate $stmt1 = $pdo->prepare("SELECT * FROM emailTable WHERE email=?"); $stmt1->execute([$Temail]); $user = $stmt1->fetch(); if($user) {
//prevents adding a duplicate email
header("Location: duplicate.html");
exit (0);
} else {
//generate Activation code
$Acode = md5(time().$Temail);
//send verification email
$emailfrom = '[email protected]'; $fromname = 'MY NAME';
$subject = 'Confirm Your Email Subscription'; $emailbody = "
<html>
<body style='background-color: #000; padding: 15px;'>
<table style='background-color: #222;'>
<tr style='background-color: #333; padding: 15px; font-size: 1.3rem;'>
<td><h2 style='color: #FFF;' align='center'>Please Verify Subscription</h2></td>
</tr>
<tr>
<td style='color: #FFF; font-size: 1.1rem;' align='center'>
<br/>
<br/>
If you didn't sign up for my email list, simply delete this message. You will not be added unless you push the button below.
<br/>
<br/>
</td>
</tr>
<tr>
<td style='color: #FFF; font-size: 1.3rem;' align='center'>
<button style='background-color: #000; width: 6rem; height: 2rem;'><a href='https://www.MYDOMAIN.com/verify.php?acode=$Acode' style='color: #F00; text-decoration: none; font-size:1rem;'>VERIFY</a></button> <br/> <br/> </td> </tr> <tr> <td style='color: #FFF; font-size: 1.1rem;' align='center'> <font style='font-size:0.8rem;'>This email was automatically generated from a mailbox that is not monitored.</font> </td> </tr> </table> </body> </html>"; $headers = "Reply-To: MY NAME <[email protected]>\r\n";
$headers .= "Return-Path: MY NAME <[email protected]>\r\n"; $headers .= "From: MY NAME <[email protected]>\r\n";
$headers .= "MIME-Version: 1.0\r\n"; $headers .= "Content-type: text/html; charset=UTF-8\r\n";
$headers .= "X-Priority: 3\r\n"; $headers .= "X-Mailer: PHP". phpversion() ."\r\n" ;
$params = '-f ' . $emailfrom;
$send = mail($Temail, $subject, $emailbody, $headers, $params); // $send should be TRUE if the mail function is called correctly if($send) {
//add the new email and other data to the database
$sql = "INSERT INTO emailTable (IP4, datetime, email, acode) VALUES (:IP4, :datetime, :email, :acode)"; $stmt2 = $pdo->prepare($sql);
$stmt2->execute(['IP4' => $userIP4, 'datetime' => $dateTime, 'email' => $Temail, 'acode' => $Acode]); $userIP4 = "";
$dateTime = ""; $Temail = "";
$Acode = "";
header("Location: success.html");
exit (0);
} else {
header("Location: invalid.html");
exit (0);
}
}
} else {
header("Location: notallowed.html");
exit (0);
}
}
}
} else {
header("Location: invalid.html");
exit (0);
}
} else {
header("Location: invalid.html");
exit (0);
}
//2 END
?>
Ameaças de segurança em mente:
1. Injeções de SQL !!! --- Soluções: Prepared Statements (PDO), usando apenas UTF-8, e incluindo "$ bpdo-> setAttribute (PDO :: ATTR_EMULATE_PREPARES, false);" na conexão do banco de dados
2. Ataques XSS !!! --- Soluções: htmlspecialchars (), Política de segurança de conteúdo (colocado em htaccess):
<FilesMatch "\.(html|php)$">
Header set Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' data: 'unsafe-inline'; media-src 'self' data: 'unsafe-inline'; connect-src 'self';"
</FilesMatch>
3. Ataques de comando do sistema operacional !!! --- Soluções: Faixa de espaço em branco (não é necessário com emails) , validando contra uma lista de desbloqueio de valores permitidos.
4. Ataques DOS !!! --- Solução: Nenhuma implementada. Não tenho certeza se algum cuidado adicional é necessário, uma vez que não há possibilidades de login no meu site.
5. PHP Email Injection !!! --- Solução: Uma Expressão Regular (a que tenho é projetada principalmente para permitir caracteres internacionais).
Além disso, eu uso um certificado SSL, SiteLock Security- Essential, CloudFlare CDN e implementei uma política DMARC em meu DNS (algo que estarei ajustando em um futuro próximo).
Respostas
Em primeiro lugar, independentemente de qualquer problema de segurança real ou imaginário, este código é uma dor de cabeça . É quase impossível obter a essência disso e responder à pergunta real, porque o código está constantemente mudando de vista e a grande quantidade de HTML atrapalha no meio do suposto código de verificação de e-mail. Verificações inúteis também contribuem para isso.
Você realmente deve reescrever seu código primeiro, para torná-lo legível. Afinal, também é uma espécie de problema de segurança - em uma região selvagem, é fácil ignorar um problema real. Dê um pouco de amor ao seu código :
- mova a conexão do banco de dados para um arquivo separado e, em seguida, apenas inclua-o. A propósito, aqui está como fazer isso corretamente . No momento, sua conexão PDO é um problema de segurança porque revelar a mensagem de erro do sistema para o mundo externo não é uma questão menor
- criar uma função para enviar e-mails, colocar todo esse
$headers .= "From: MY NAME <[email protected]>\r\n";negócio aí. em seguida, coloque a função em um arquivo separado e apenas inclua-a. - mova o código para enviar o e-mail real para uma função e coloque essa função na parte inferior do código. Portanto, isso não distrairá o leitor do algoritmo principal.
- livrar-se do código inútil. Verificar REMOTE_ADDR não faz sentido, não há situação em que seria inválido. htmlspecialchars também é inútil aqui. e limpar suas variáveis, ou seja
$userIP4 = "";, também - livre-se dessa escada de código. Dadas as suas condições, pare a execução de qualquer maneira - por que não apenas testar o resultado negativo e parar a execução?
Então, em vez de
if (condition) {
if (condition2) {
do something;
} else {
display error;
die;
}
} else {
display error;
die;
}
apenas escreva
if (!condition) {
display error;
die;
}
if (!condition2) {
display error;
die;
}
do something;
Depois de tornar seu código adequado para a revisão, você está convidado a fazer uma nova pergunta sobre suas questões de segurança. De longe, consegui localizar pelo menos um.
Divulgação de informações desnecessárias
Não faça isso:
$headers .= "X-Mailer: PHP". phpversion() ."\r\n" ;
Isso aparecerá nos cabeçalhos do e-mail e ficará visível para o destinatário. Não é da conta de ninguém qual versão do PHP você está executando. Caso você esteja executando uma versão do PHP com vulnerabilidades, isso fornece aos hackers uma visão dos seus sistemas para uma exploração personalizada. Só não copie e cole o código sem entender as implicações ...
IPv4 e IPv6
Eu não entendo o objetivo de gethostbyname. Você já tem o endereço IP, portanto, pode armazená-lo.
Tem certeza de que seu servidor não pode ser acessado na Internet pelo IPv6? Você pode querer testar seu site no IPv6 adicionando uma entrada à configuração de DNS, por exemplo, ipv6.yoursite.com com um registro AAAA e nenhum registro A.
Randomization
O código de verificação não é realmente aleatório :
$Acode = md5(time().$Temail);
hora - Retorna o carimbo de data / hora Unix atual
Alguém (um hacker) que conhece sua fórmula será capaz de aplicar força bruta ao código de verificação porque ele segue um padrão previsível. Você tem muitas opções melhores para gerar strings razoavelmente aleatórias.
Misc
Eu provavelmente reordenaria algum código, por exemplo, salvaria o registro no banco de dados antes de enviar o e-mail. Se o banco de dados travar por algum motivo ou a conexão for perdida, você já enviou um e-mail com um código de verificação que não está gravado em nenhum lugar e não funcionará - o que é confuso para o usuário. Se o registro não foi salvo por algum motivo, notifique o usuário de que houve um erro (e notifique você também) e não prossiga.
O envio do e-mail é provavelmente menos provável de travar, porque geralmente vai para uma fila de e-mail para ser tratado pelo MTA em seu servidor.
É possível que o e-mail não chegue ou tenha sido descartado ou protegido por spam, portanto, deve ser possível para o usuário solicitar um novo código após algum tempo.
Com relação à conexão db e tratamento de erros, leia esta resposta . Você nunca deve revelar detalhes de erros gerados pelo sistema para seus usuários finais - esses detalhes são para você e mais ninguém. Eu recomendo uma
requirechamada, mas não antes que o envio do usuário se qualifique para seu uso.Não há absolutamente nenhuma razão para modificar ou higienizar o endereço de e-mail de entrada. Você pode gostar de espaços em branco
trim(), mas, honestamente, quem realmente cometerá o erro de adicionar um espaço não autorizado? Eu nunca ... nunca.Para consistência, eu sempre escrevo minhas ramificações condicionais negativas / falha / falsas antes das ramificações positivas / sucesso / verdadeiras. Dessa forma, você (ou outros desenvolvedores) saberão que quanto mais baixo for o progresso do script, mais bem-sucedido será o fluxo e os recursos adicionais poderão ser inicializados / declarados.
Não gere o timestamp para a linha do banco de dados no php. Você nem precisa mencioná-lo em seu sql. Defina o seu
emailTable'sdatetimecoluna paraDEFAULTaCURRENT_TIMESTAMP.https://stackoverflow.com/q/168736/2943403Se a expectativa é permitir caracteres multibyte em endereços de e-mail (
//allow international characters), seu regex nãoucontém o sinalizador de nicode. Dito isso, não recomendo usar regex para tentar analisar / validar um endereço de e-mail porque, à medida que seu padrão de validação melhora sua precisão, a legibilidade e a manutenção despencam. Eu recomendofilter_var()https://stackoverflow.com/q/12026842/2943403$email_hostjá contém o domínio, portanto, não execute a cirurgia novamente comexplode()/array_pop().SELECT * FROM emailTable WHERE email=?está pedindo muitos dados. Você só precisa deCOUNT(), não dos dados da linha. Dessa forma, você busca apenas a contagem (que será zero ou um), então a condição é muito simples e legível.Se você quiser secar as linhas de "cabeçalho e morrer", poderá criar uma pequena função auxiliar que receba o texto Localização e depois chame o
header(), entãodie()s.Eu recomendaria usar a sintaxe
<<<HTML ... HTML;(HEREDOC) para que você possa usar aspas duplas em suas variáveis de marcação e embutidas. Como alternativa, você pode usarsprintf(), mas isso é menos atraente com apenas uma variável.Desdeeu recomendo, criando uma matriz de linhas$headerslinhas são delimitadas por\r\n,, em seguida,. O quarto parâmetroimplode()ing com\r\na ser mais SECOmail()são os dados do cabeçalho que podem receber uma matriz.Eu não recomendo o nativo
mail(). Eu sempre incluo o PHPMailer em todos os meus projetos para facilidade de uso e funcionalidade.Estou preocupado com a ambigüidade se
invalid.html. Semail()falhar, o usuário não saberá se há algo que ele pode consertar ou não.$userIP4 = ""; $dateTime = ""; $Temail = ""; $Acode = "";tudo isso é um absurdo inútil, apenas omita tudo isso.Vasculhe seu script e pesquise variáveis de uso único. Quando você vê uma variável declarada usada apenas uma vez, não precisa declará-la.