Parola Sıfırlama Nasıl Uygulanır?


81

ASP.NET'te bir uygulama üzerinde çalışıyorum ve Password Resetkendi işlevimi devralmak istersem özellikle bir işlevi nasıl uygulayabileceğimi merak ediyordum .

Özellikle şu sorularım var:

  • Kırılması zor bir Benzersiz Kimlik oluşturmanın iyi bir yolu nedir?
  • Eklenmiş bir zamanlayıcı olmalı mı? Eğer öyleyse, ne kadar sürmeli?
  • IP adresini kaydetmeli miyim? Fark eder mi?
  • "Parola Sıfırlama" ekranının altında hangi bilgileri istemeliyim? Sadece E-posta adresi mi? Ya da e-posta adresi artı 'bildikleri' bazı bilgiler? (Favori takım, köpek yavrusu adı vb.)

Dikkat etmem gereken başka hususlar var mı?

NB : Diğer sorular , teknik uygulama tamamen gözden kaçırıldı . Gerçekten de kabul edilen cevap, kanlı ayrıntıların üzerinde parlıyor. Umarım bu soru ve sonraki cevaplar kanlı ayrıntılara girecektir ve bu soruyu çok daha dar bir şekilde ifade ederek cevapların daha az 'tüysüz' ve daha 'kanlı' olmasını umuyorum.

Düzenleme : Böyle bir tablonun SQL Server'da nasıl modelleneceği ve işleneceği veya bir yanıta yönelik herhangi bir ASP.NET MVC bağlantısı ile ilgili cevaplar takdir edilecektir.


ASP.NET MVC, varsayılan ASP.NET kimlik doğrulama sağlayıcısını kullanır, bu nedenle bu s etrafında bulduğunuz kod örnekleri, amaçlarınız için uygun olana kadar olmalıdır.
paulwhit

Yanıtlar:


66

Burada birçok iyi cevap var, hepsini tekrarlamakla uğraşmayacağım ...

Buradaki hemen hemen her cevapta yanlış da olsa tekrarlanan bir konu dışında:

Kılavuzlar (gerçekçi olarak) benzersizdir ve istatistiksel olarak tahmin edilmesi imkansızdır.

Bu doğru değildir, GUID'ler çok zayıf tanımlayıcılardır ve bir kullanıcının hesabına erişime izin vermek için KULLANILMAMALIDIR .
Yapıyı incelerseniz, toplamda en fazla 128 bit elde edersiniz ... ki bu günümüzde pek düşünülmüyor.
Bunun dışında ilk yarısı tipik olarak değişmez (üretim sistemi için) ve kalanların yarısı zamana bağlıdır (veya benzer başka bir şey).
Sonuç olarak, çok zayıf ve kolayca zorlanabilen bir mekanizmadır.

O yüzden bunu kullanmayın!

Bunun yerine, şifreleme açısından güçlü bir rasgele sayı üreteci ( System.Security.Cryptography.RNGCryptoServiceProvider) kullanın ve en az 256 bit ham entropi elde edin.

Diğer sayısız yanıtın sağladığı gibi geri kalanı.


6
Kesinlikle katılıyorum, bildiğim kadarıyla, GUID'ler asla kriptografik olarak güçlü ve tahmin edilmesi imkansız olacak şekilde tasarlanmadı.
Jan Soltis

5
iyi söylendi, AFAIK MSDN, GUID'in güvenlik için kullanılmaması gerektiğini açıkça belirtir.
dr. kötü

2
Sürüm 4 UUID'leri Windows'ta 2000'den beri kullanılmaktadır: .NET 4 GUID'leri nasıl oluşturulur? - Yığın Taşması . İçlerinde 122 rastgele bitleri var ve bence NIST önerileriyle uyumlu. CryptGenRandom'a göre, yerel bir saldırıya karşı çok kötü bir güvenlik açığı vardı - Wikipedia 2008 yılına kadar Vista ve XP'de düzeltildi. Peki GUID'lerin mevcut kullanımıyla ilgili sorunları nerede görüyorsunuz?
nealmcb

4
Bu "Eski Yeni Şey" blogu, kullanımdan kaldırılmış sürüm 1 UUID'leri açıklıyor ve blog gönderisinden 10 yıl önce, 1998'de süresi dolan bir İnternet Taslağına (asla yapmamanız gereken bir şey) atıfta bulunuyor. Gelecekte onlara şüpheyle yaklaşırdım. Bu savaşları uzun zaman önce yaptık ve çoğunu kazandık. Kripto-rastgele bir kaynağa temiz bir API çağrısı kullanmanın çok daha iyi olduğunu hala kabul ediyorum, ancak 4.
sürümdeki

1
Değeri ne olursa olsun, bu "parola nasıl sıfırlanır" sorusuna yanıt vermez. GUID'ler hakkında harika noktalar kustunuz.
Rex Whitten

67

DÜZENLEME 2012/05/22: Bu popüler cevabın bir devamı olarak, artık bu prosedürde GUID'leri kendim kullanmıyorum. Diğer popüler cevap gibi, şimdi URL'de gönderilecek anahtarı oluşturmak için kendi hash algoritmamı kullanıyorum. Bunun daha kısa olma avantajı da vardır. Bunları oluşturmak için genellikle bir SALT kullandığım System.Security.Cryptography'ye bakın.

İlk olarak, kullanıcının şifresini hemen sıfırlamayın.

İlk olarak, kullanıcının şifresini talep ettiklerinde hemen sıfırlamayın. Bu, birisi e-posta adreslerini (yani şirketteki e-posta adresinizi) tahmin edebileceği ve şifreleri istediğiniz zaman sıfırlayabileceği için bir güvenlik ihlalidir. Bu günlerde en iyi uygulamalar genellikle kullanıcının e-posta adresine gönderilen ve sıfırlamak istediklerini onaylayan bir "onay" bağlantısını içerir. Bu bağlantı, benzersiz anahtar bağlantısını göndermek istediğiniz yerdir. Benimkini şöyle bir bağlantıyla gönderiyorum:domain.com/User/PasswordReset/xjdk2ms92

Evet, bağlantıda bir zaman aşımı ayarlayın ve anahtarı ve zaman aşımını arka ucunuzda saklayın (ve kullanıyorsanız tuz). 3 günlük zaman aşımı normaldir ve kullanıcıyı sıfırlama isteğinde bulunduğunda web düzeyinde 3 günlük zaman aşımına uğrattığınızdan emin olun.

Benzersiz bir hash anahtarı kullanın

Önceki cevabım bir GUID kullanmamı söyledi. Şimdi bunu herkese rastgele oluşturulmuş bir hash kullanmalarını tavsiye etmek için düzenliyorum, örneğin RNGCryptoServiceProvider. Ve hash'deki tüm "gerçek kelimeleri" kaldırdığınızdan emin olun. Bir kadının geliştiricinin yaptığı "rasgele olduğunu varsayalım" hashed anahtarında belirli bir "c" kelimesini aldığı özel bir telefon görüşmesini hatırlıyorum. Doh!

Tüm prosedür

  • Kullanıcı şifreyi "sıfırla" yı tıklar.
  • Kullanıcıdan bir e-posta istenir.
  • Kullanıcı e-postayı girer ve gönder'i tıklar. E-postayı onaylamayın veya reddetmeyin, çünkü bu da kötü bir uygulamadır. "E-posta doğrulanmışsa bir şifre sıfırlama isteği gönderdik" demeniz yeterlidir. ya da şifreli bir şey.
  • Öğesinden bir karma oluşturursunuz RNGCryptoServiceProvider, bunu bir ut_UserPasswordRequeststabloda ayrı bir varlık olarak depolar ve kullanıcıya geri bağlanırsınız. Böylece, eski istekleri takip edebilir ve kullanıcıyı eski bağlantıların süresinin dolduğu konusunda bilgilendirebilirsiniz.
  • Bağlantıyı e-postaya gönderin.

Kullanıcı bağlantıyı alır, beğenir http://domain.com/User/PasswordReset/xjdk2ms92ve tıklar.

Bağlantı doğrulanırsa, yeni bir şifre sorarsınız. Basittir ve kullanıcı kendi şifresini belirleyebilir. Veya burada kendi şifreli şifrenizi ayarlayın ve yeni şifrelerini buradan bildirin (ve onlara e-posta ile gönderin).


1
Merak ediyordum, eğer gerçek kullanıcı şifresi hash edilmişse, neden yeni bir HASH Anahtarı oluşturmalı? Hashed parolayı geçirerek parolayı sıfırlamak için bir bağlantı içeren bir kullanıcıya e-posta göndermek doğru olmaz mı? Hashing uygulanmış parola geri alınamaz, kullanıcı bağlantıya tıkladığında, sunucu hashing uygulanmış parolayı alır, gerçek depolanmış parola ile karşılaştırır ve ardından kullanıcının parolayı değiştirmesine izin verir.
Daniel

Ve bununla ilgili güzel bir başka şey de, Zaman Aşımını ayarlamanıza gerek olmamasıdır, kullanıcı parolayı değiştirdiğinde, veritabanında depolanan karma parola değiştirildiği için eski bağlantı otomatik olarak artık geçerli olmayacaktır.
Daniel

@Daniel bu gerçekten kötü bir fikir. Google’a "kaba kuvvet saldırıları" terimini kullanmanız gerektiğini düşünüyorum. Ayrıca, süresinin sona ermesini YAPMAK istemenizin nedeni, birinin e-postasının bir yıl sonra ele geçirilmesi (ve asla sıfırlamaması) durumunda, bilgisayar korsanı şifre değiştirme hakkını elde eder.
eduncan911

@ educan911. Kaba kuvvet saldırılarını biliyorum, ancak aynı zamanda Karma anahtarına, Kötü Amaçlı kişiye erişebilmek için e-postaya erişmesi gerekir ve buna erişimi varsa, karma parolayı geri almaya gerek yoktur. Ayrıca, bunu neredeyse imkansız hale getirmek için, şifrelenmiş şifreyi karıştırabilir veya daha da iyisi, şifreyi daha fazla bir şeyle hash edebilirsiniz. Sana katılmıyorum, sadece bunun hakkında bir beyin fırtınası yapmaya çalışıyorum
Daniel

8

Öncelikle, kullanıcı hakkında zaten bildiklerinizi bilmemiz gerekir. Açıkçası, bir kullanıcı adınız ve eski bir şifreniz var. Başka ne biliyorsun? Mail adresin var mı? Kullanıcının en sevdiği çiçekle ilgili verileriniz var mı?

Bir kullanıcı adınız, şifreniz ve çalışan bir e-posta adresiniz olduğunu varsayarak, kullanıcı tablonuza iki alan eklemeniz gerekir (bunun bir veritabanı tablosu olduğu varsayılarak): new_passwd_expire adlı bir tarih ve new_passwd_id dizesi.

Kullanıcının e-posta adresine sahip olduğunuzu varsayarsak, birisi şifre sıfırlama istediğinde kullanıcı tablosunu aşağıdaki şekilde güncellersiniz:

new_passwd_expire = now() + some number of days
new_passwd_id = some random string of characters (see below)

Ardından, kullanıcıya bu adresten bir e-posta gönderirsiniz:

Sevgili falan

Birisi <web sitenizin adı> adresindeki <kullanıcı adı> kullanıcı hesabı için yeni bir şifre istedi. Bu şifre sıfırlamayı talep ettiyseniz, şu bağlantıyı izleyin:

http://example.com/yourscript.lang?update=<new_password_id >

Bu bağlantı çalışmazsa, http://example.com/yourscript.lang adresine gidebilir ve şu forma şunu girebilirsiniz: <new_password_id>

Parola sıfırlama talebinde bulunmadıysanız, bu e-postayı göz ardı edebilirsiniz.

Teşekkürler yada yada

Şimdi, yourscript.lang kodlama: Bu komut dosyasının bir forma ihtiyacı var. Değişken güncellemesi URL'ye aktarılırsa, formda yalnızca kullanıcının kullanıcı adı ve e-posta adresi istenir. Güncelleme geçilmezse, kullanıcı adı, e-posta adresi ve e-postayla gönderilen kimlik kodunu ister. Ayrıca yeni bir şifre de istersiniz (tabii ki iki kez).

Kullanıcının yeni şifresini doğrulamak için, kullanıcı adı, e-posta adresi ve kimlik kodunun tamamen eşleştiğini, isteğin süresinin dolmadığını ve iki yeni şifrenin eşleştiğini doğrularsınız. Başarılı olursa, kullanıcının parolasını yeni parolayla değiştirirsiniz ve parola sıfırlama alanlarını kullanıcı tablosundan temizlersiniz. Ayrıca kullanıcının oturumunu kapattığınızdan / oturum açma ile ilgili tüm çerezleri temizlediğinizden ve kullanıcıyı oturum açma sayfasına yönlendirdiğinizden emin olun.

Esasen, new_passwd_id alanı yalnızca parola sıfırlama sayfasında çalışan bir paroladır.

Olası bir iyileştirme: e-postadan <kullanıcı adı> 'nı kaldırabilirsiniz. "Birisi bu e-posta adresindeki bir hesap için şifre sıfırlama talebinde bulundu ...." Böylece kullanıcı adı, e-postanın ele geçirilmesi durumunda yalnızca kullanıcının bildiği bir şey haline gelir. Bu şekilde başlamadım çünkü birisi hesaba saldırıyorsa kullanıcı adını zaten biliyordur. Bu ek belirsizlik, kötü niyetli birinin e-postayı ele geçirmesi durumunda ortadaki adam fırsatlarını durdurur.

Sorularınıza gelince:

rastgele dizge oluşturmak: Son derece rastgele olması gerekmez. Herhangi bir GUID oluşturucu veya hatta md5 (concat (salt, current_timestamp ())) yeterlidir, burada salt, zaman damgası hesabı oluşturulmuş gibi kullanıcı kaydındaki bir şeydir. Kullanıcının göremeyeceği bir şey olmalı.

timer: Evet, veritabanınızı aklı başında tutmak için buna ihtiyacınız var. Bir haftadan fazla bir süre gerçekten gerekli değildir, ancak bir e-posta gecikmesinin ne kadar süreceğini asla bilemediğiniz için en az 2 gün.

IP Adresi: E-posta günlerce gecikebileceğinden, IP adresi doğrulama için değil, yalnızca oturum açmak için yararlıdır. Günlüğe kaydetmek istiyorsanız, bunu yapın, aksi takdirde ihtiyacınız olmaz.

Ekranı Sıfırla: Yukarıya bakın.

Umarım kapsar. İyi şanslar.


Potansiyel bir saldırgan, içeri girmek için mevcut tarih damgasının MD5'ini kullanamaz mı?
George Stocker

Tel üzerinden e-posta ile şifre göndermemenizi şiddetle tavsiye ederim. Kullanıcıların çoğu, bu e-postaları silinmemiş olarak bırakır ve bu da bir güvenlik ihlali anlamına gelir - bazıları her seferinde 'en sevdikleri' e-postalardan kopyalayıp yapıştırmak isteyecektir. Kullanıcıların şirket posta sunucusunun sertifikasının süresi dolmuşsa ve trafik koklanmışsa ne olur? Bu olası ihlali en aza indirmek için, (1) bu belirli şifrenin kısa bir sona erme süresi - 1 saat ayarlamak ve (2) kullanıcıyı bir sonraki oturum açma işleminde güncellemeye zorlamaktır.
Ognyan Dimitrov

Ognyan, e-postayla gönderilen şifre yalnızca bir kez çalışır. Giriş yaptıktan sonra şifrelerini değiştirmeleri gerekir ve e-posta kullanıcı oturum açma adını içermez. Yani hayır, her seferinde kopyalayıp yapıştıramazlar. Parola sıfırlandıktan sonra saldırgana HİÇBİR ŞEY kazandıracak anlamsız bir harf / rakam dizisi olduğundan, e-postayı silmemek bir güvenlik sorunu değildir.
jmucchiello

3

Kaydın e-posta adresine gönderilen bir GUID, çoğu sıradan uygulama için muhtemelen yeterlidir - zaman aşımı daha da iyidir.

Sonuçta, eğer kullanıcının e-posta kutusu ele geçirildiyse (yani bir bilgisayar korsanı, e-posta adresi için oturum açma / parolaya sahipse), bu konuda yapabileceğiniz pek bir şey yoktur.


2

Bir bağlantıyla kullanıcıya bir e-posta gönderebilirsiniz. Bu bağlantı, tahmin edilmesi zor bazı dizeler içerir (GUID gibi). Sunucu tarafında, kullanıcıya gönderdiğiniz dizinin aynısını da saklarsınız. Artık kullanıcı bağlantıya bastığında, db girişinizde aynı gizli dizeyi bulabilir ve şifresini sıfırlayabilirsiniz.


Daha fazla ayrıntı yardımcı olacaktır.
George Stocker

2

1) Benzersiz kimliği oluşturmak için Güvenli Karma Algoritmasını kullanabilirsiniz. 2) zamanlayıcı takılı mı? Sıfırlama pwd bağlantısı için bir süre sonu mu demek istediniz? Evet, bir Geçerlilik bitiş setine sahip olabilirsiniz 3) Doğrulamak için e-posta kimliği dışında daha fazla bilgi isteyebilirsiniz .. Doğum tarihi veya bazı güvenlik soruları gibi 4) Ayrıca rastgele karakterler oluşturabilir ve bunu da istekle birlikte girmenizi isteyebilirsiniz .. parola isteğinin bazı casus yazılımlar veya buna benzer şeyler tarafından otomatikleştirilmediğinden emin olmak için ..


0

ASP.NET Identity için Microsoft kılavuzunun iyi bir başlangıç ​​olduğunu düşünüyorum.

https://docs.microsoft.com/en-us/aspnet/identity/overview/features-api/account-confirmation-and-password-recovery-with-aspnet-identity

ASP.NET Identity için kullandığım kod:

Web.Config:

<add key="AllowedHosts" value="example.com,example2.com" />

AccountController.cs:

[Route("RequestResetPasswordToken/{email}/")]
[HttpGet]
[AllowAnonymous]
public async Task<IHttpActionResult> GetResetPasswordToken([FromUri]string email)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    var user = await UserManager.FindByEmailAsync(email);
    if (user == null)
    {
        Logger.Warn("Password reset token requested for non existing email");
        // Don't reveal that the user does not exist
        return NoContent();
    }

    //Prevent Host Header Attack -> Password Reset Poisoning. 
    //If the IIS has a binding to accept connections on 80/443 the host parameter can be changed.
    //See https://security.stackexchange.com/a/170759/67046
    if (!ConfigurationManager.AppSettings["AllowedHosts"].Split(',').Contains(Request.RequestUri.Host)) {
            Logger.Warn($"Non allowed host detected for password reset {Request.RequestUri.Scheme}://{Request.Headers.Host}");
            return BadRequest();
    }

    Logger.Info("Creating password reset token for user id {0}", user.Id);

    var host = $"{Request.RequestUri.Scheme}://{Request.Headers.Host}";
    var token = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
    var callbackUrl = $"{host}/resetPassword/{HttpContext.Current.Server.UrlEncode(user.Email)}/{HttpContext.Current.Server.UrlEncode(token)}";

    var subject = "Client - Password reset.";
    var body = "<html><body>" +
               "<h2>Password reset</h2>" +
               $"<p>Hi {user.FullName}, <a href=\"{callbackUrl}\"> please click this link to reset your password </a></p>" +
               "</body></html>";

    var message = new IdentityMessage
    {
        Body = body,
        Destination = user.Email,
        Subject = subject
    };

    await UserManager.EmailService.SendAsync(message);

    return NoContent();
}

[HttpPost]
[Route("ResetPassword/")]
[AllowAnonymous]
public async Task<IHttpActionResult> ResetPasswordAsync(ResetPasswordRequestModel model)
{
    if (!ModelState.IsValid)
        return NoContent();

    var user = await UserManager.FindByEmailAsync(model.Email);
    if (user == null)
    {
        Logger.Warn("Reset password request for non existing email");
        return NoContent();
    }            

    if (!await UserManager.UserTokenProvider.ValidateAsync("ResetPassword", model.Token, UserManager, user))
    {
        Logger.Warn("Reset password requested with wrong token");
        return NoContent();
    }

    var result = await UserManager.ResetPasswordAsync(user.Id, model.Token, model.NewPassword);

    if (result.Succeeded)
    {
        Logger.Info("Creating password reset token for user id {0}", user.Id);

        const string subject = "Client - Password reset success.";
        var body = "<html><body>" +
                   "<h1>Your password for Client was reset</h1>" +
                   $"<p>Hi {user.FullName}!</p>" +
                   "<p>Your password for Client was reset. Please inform us if you did not request this change.</p>" +
                   "</body></html>";

        var message = new IdentityMessage
        {
            Body = body,
            Destination = user.Email,
            Subject = subject
        };

        await UserManager.EmailService.SendAsync(message);
    }

    return NoContent();
}

public class ResetPasswordRequestModel
{
    [Required]
    [Display(Name = "Token")]
    public string Token { get; set; }

    [Required]
    [Display(Name = "Email")]
    public string Email { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 10)]
    [DataType(DataType.Password)]
    [Display(Name = "New password")]
    public string NewPassword { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm new password")]
    [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.