PHP'de E-posta Doğrulaması
Bu komut dosyası, kullanıcı e-posta girişini doğrulamak için yeterli mi?
<?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
?>
Akılda tutulan güvenlik tehditleri:
1. SQL Enjeksiyonları !!! --- Çözümler: Hazırlanmış İfadeler (PDO), yalnızca UTF-8 kullanılarak ve "$ bpdo-> setAttribute (PDO :: ATTR_EMULATE_PREPARES, false);" dahil veritabanı bağlantısında
2. XSS Saldırıları !!! --- Çözümler: htmlspecialchars (), İçerik Güvenliği Politikası (htaccess'e yerleştirilir):
<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. OS Komutu Saldırıları !!! --- Çözümler: Beyaz alanları şeritleme (e-postalarda gerekli değildir) , izin verilen değerler beyaz listeye göre doğrulama.
4. DOS Saldırıları !!! --- Çözüm: Hiçbiri uygulanmadı. Web sitemde oturum açma olanağı olmadığı için herhangi bir ek önlem gerekip gerekmediğinden emin değilim.
5. PHP E-posta Enjeksiyonu !!! --- Çözüm: Normal Bir İfade (Benim sahip olduğum ifade çoğunlukla uluslararası karakterlere izin vermek için tasarlanmıştır).
Ek olarak, bir SSL Sertifikası, SiteLock Security-Essential, CloudFlare CDN kullanıyorum ve DNS'ime bir DMARC Politikası uyguladım (öngörülebilir gelecek için ince ayar yapacağım bir şey).
Yanıtlar
Öncelikle ve en önemlisi, herhangi bir gerçek veya hayali güvenlik sorunu ne olursa olsun, bu kod göze bir ağrıdır . Bunun özünü anlamak ve asıl soruyu yanıtlamak neredeyse imkansızdır çünkü kod sürekli olarak gözden kaybolur ve sözde e-posta doğrulama kodunun ortasında büyük miktarda HTML araya girer. Yararsız doğrulamalar da buna katkıda bulunur.
Okunabilir hale getirmek için önce kodunuzu gerçekten yeniden yazmalısınız. Sonuçta, bu aynı zamanda bir tür güvenlik sorunu - böylesi bir vahşi doğada gerçek bir sorunu gözden kaçırmak kolaydır. Kodunuza biraz sevgi gösterin :
- veritabanı bağlantısını ayrı bir dosyaya taşıyın ve ardından ekleyin. Bu arada, işte nasıl düzgün yapılacağı . Şu anda PDO bağlantınız bir güvenlik sorunudur çünkü sistem hata mesajını dış dünyaya açıklamak önemsiz değildir.
- e-posta göndermek için bir işlev oluşturun, tüm bu
$headers .= "From: MY NAME <[email protected]>\r\n";işi oraya koyun . daha sonra işlevin kendisini ayrı bir dosyaya koyun ve ardından ekleyin. - gerçek e-postayı bir işleve göndermek için kodu taşıyın ve bu işlevi kodun altına koyun. Yani okuyucuyu ana algoritmadan uzaklaştırmaz.
- işe yaramaz koddan kurtulun. REMOTE_ADDR'ın işaretlenmesi bir anlam ifade etmiyor, geçersiz olacağı bir durum yok. htmlspecialchars burada da işe yaramaz. ve, ie sizin değişkenleri temizleyerek
$userIP4 = "";sıra - bir kodun merdiveninden kurtulun. Koşullarınız göz önüne alındığında, yine de yürütmeyi durdurun - neden sadece olumsuz sonuç için test edip yürütmeyi durdurmuyorsunuz?
Yani yerine
if (condition) {
if (condition2) {
do something;
} else {
display error;
die;
}
} else {
display error;
die;
}
sadece yaz
if (!condition) {
display error;
die;
}
if (!condition2) {
display error;
die;
}
do something;
Kodunuzu incelemeye uygun hale getirdikten sonra, güvenlik endişelerinizle ilgili yeni bir soru sorabilirsiniz. Şimdiye kadar en az birini tespit edebildim.
Gereksiz bilgi ifşası
Bunu yapma:
$headers .= "X-Mailer: PHP". phpversion() ."\r\n" ;
Bu, posta başlıklarında görünecek ve alıcı tarafından görülebilecektir. PHP'nin hangi sürümünü çalıştırdığınız kimseyi ilgilendirmez. Güvenlik açıkları olan bir PHP sürümünü çalıştırıyorsanız, bu, bilgisayar korsanlarına özel bir açıktan yararlanma için sistemleriniz hakkında bilgi sağlar. Bunun sonuçlarını anlamadan kodu kopyalayıp yapıştırmayın ...
IPv4 ve IPv6
Gethostbyname'in amacını anlamıyorum. IP adresiniz zaten var, bu yüzden saklayabilirsiniz.
Sunucunuza IPv6 üzerinden İnternet üzerinden erişilemediğinden emin misiniz? DNS yapılandırmanıza, tek bir AAAA kaydı olan ve A kaydı olmayan ipv6.yoursite.com gibi bir giriş ekleyerek sitenizi IPv6 üzerinden test etmek isteyebilirsiniz.
Randomizasyon
Doğrulama kodu gerçekten rastgele değil :
$Acode = md5(time().$Temail);
time - Geçerli Unix zaman damgasını döndürür
Formülünüzü bilen biri (bir bilgisayar korsanı), tahmin edilebilir bir model izlediği için doğrulama kodunu zorla çalıştırabilir. Makul ölçüde rastgele dizeler oluşturmak için çok daha iyi seçeneğiniz var.
Çeşitli
Muhtemelen bazı kodları yeniden sıralarım, örneğin postayı göndermeden önce kaydı veritabanına kaydederdim. Veritabanı herhangi bir nedenle çökerse veya bağlantı kesilirse, herhangi bir yerde kaydedilmeyen ve çalışmayan bir doğrulama kodu içeren bir E-posta göndermişsinizdir - bu, kullanıcı için kafa karıştırıcıdır. Kayıt herhangi bir nedenle kaydedilmediyse, kullanıcıya bir hata olduğunu bildirin (ve kendinize de bildirin) ve daha fazla ilerlemeyin.
Postanın gönderilmesi, genellikle sunucunuzdaki MTA tarafından işlenecek bir posta kuyruğuna gittiği için muhtemelen daha az çökecektir.
Postanın ulaşmaması, atılmış veya spam tuzağına düşmüş olması mümkündür, bu nedenle kullanıcının bir süre sonra yeni bir kod talep etmesi mümkün olmalıdır.
Db bağlantısı ve hata işleme ile ilgili olarak, lütfen bu yanıtı okuyun . Sistem tarafından oluşturulan hata ayrıntılarını son kullanıcılarınıza asla ifşa etmemelisiniz - bu ayrıntılar sizin içindir ve başkası değildir. Bir
requirearama öneririm , ancak kullanıcının gönderimi kullanım için uygun hale gelmeden önce değil.Gelen e-posta adresini mutasyona uğratmak veya sterilize etmek için kesinlikle hiçbir neden yoktur. Beyaz boşlukları sevebilirsiniz
trim(), ama dürüst olmak gerekirse, sahtekar bir alan ekleme hatasını kim yapacak? Ben asla ... asla.Tutarlılık için, her zaman olumsuz / başarısız / yanlış koşullu dallarımı olumlu / başarılı / doğru dallardan önce yazarım. Bu şekilde, siz (veya diğer geliştiriciler) komut dosyası ne kadar düşük ilerlerse, akışın o kadar başarılı olduğunu ve ek kaynakların başlatılabileceğini / beyan edilebileceğini bileceksiniz.
Php'de db satırı için zaman damgası oluşturmayın. Sql'nizde bahsetmenize bile gerek yok. Senin Set
emailTable'indatetimesütunDEFAULTiçinCURRENT_TIMESTAMP.https://stackoverflow.com/q/168736/2943403Beklentiniz, e-posta adreslerinde (
//allow international characters) çok baytlı karakterlere izinuvermekse , normal ifadenizde nikot bayrağı eksiktir . Bununla birlikte, bir e-posta adresini ayrıştırmaya / doğrulamaya çalışmak için normal ifadeyi kullanmanızı önermiyorum çünkü doğrulama modeliniz doğruluğunu, okunabilirliği ve sürdürülebilirliği artırdıkça düşüyor. Ben tavsiye ediyorumfilter_var()https://stackoverflow.com/q/12026842/2943403$email_hostalanı zaten içerir, bu nedenleexplode()/ ile tekrar ameliyat yapmayınarray_pop().SELECT * FROM emailTable WHERE email=?çok fazla veri istiyor. YalnızcaCOUNT()satırın verilerine değil, ihtiyacınız olan . Bu şekilde, yalnızca sayımı alırsınız (sıfır veya bir olacaktır), böylece koşul çok basit ve okunabilir hale gelir."Başlık ve kalıp" satırlarını KURUTMAK istiyorsanız, Konum metnini alan ve ardından
header(), sonradie()s.İşaretleme ve satır içi değişkenlerinizde çift tırnak kullanabilmeniz için
<<<HTML ... HTML;(HEREDOC) sözdizimini kullanmanızı tavsiye ederim . Alternatif olarak kullanabilirsinizsprintf(), ancak bu tek bir değişkenle daha az zorlayıcıdır.Yanaben çizgilerin bir dizi oluşturulurken, tavsiye$headershatları herkes tarafından sınırlanmış\r\n,ardından,. Dördüncü parametre,implode()ile ing\r\ndaha KURU olmakmail()bir diziyi alabilen başlık verileridir.Yerlileri tavsiye etmiyorum
mail(). Kullanım kolaylığı ve işlevsellik için her zaman tüm projelerime PHPMailer kurarım.Eğer belirsizlik konusunda endişeliyim
invalid.html. Eğermail()başarısız onlar düzeltmek veya olamayacağını bir şey varsa, o zaman kullanıcı bilmeyecektir.$userIP4 = ""; $dateTime = ""; $Temail = ""; $Acode = "";bunların hepsi gereksiz saçmalık, sadece hepsini atlayın.Komut dosyanızı gözden geçirin ve tek kullanımlık değişkenleri arayın. Bildirilmiş bir değişkenin yalnızca bir kez kullanıldığını gördüğünüzde, onu bildirmeniz gerekmez.