我创建了单页应用程序,该应用程序需要username
和password
进行用户身份验证。系统中的每个帐户都由管理员创建。用户没有单击链接和创建帐户的选项。在研究和寻找最佳解决方案之后,我改变的第一件事是用户如何恢复密码。下面是有关该逻辑如何工作的简短架构。用户首先必须单击链接Forgot Password
输入其电子邮件并提交表单。他们将看到以下消息:
An email has been sent to example@gmail.com with further instructions.
将执行此过程的函数如下所示:
cfstoredproc( procedure="CheckEmail", datasource=dsn ) {
cfprocparam( maxlength=80, null=!len(trim(arguments.email)), cfsqltype="cf_sql_varchar", dbvarname="@Email", value=trim(arguments.email) );
cfprocresult( name="EmailResult" );
}
if( EmailResult.recordCount EQ 1) {
// Generate new token
local.newToken = createUUID();
// Build URL with token parameter and add hashed value
local.theUrl = "https://example.com/Reset.cfm?token=" & local.newToken;
// Set expiration time (30 minutes)
local.Expires = DateAdd("n", 30, now());
cfstoredproc( procedure="SaveToken", datasource=dsn ) {
cfprocparam( maxlength=80, null=!len(trim(arguments.email)), cfsqltype="cf_sql_varchar", dbvarname="@Email", value=trim(arguments.email) );
cfprocparam( maxlength=35, null=!len(trim(local.newToken)), cfsqltype="cf_sql_varchar", dbvarname="@Token", value=trim(local.newToken) );
cfprocparam( null=!len(trim(local.Expires)), cfsqltype="cf_sql_timestamp", dbvarname="@Expires", value=trim(local.Expires) );
cfprocresult( name="TokenResult" );
}
if ( len(TokenResult.RecID) ) {
savecontent variable="mailBody"{
writeOutput('<br>Here is your password reset link: <a href="' & theUrl & '">Click here</a> as soon as possible and change your password.<br>' );
}
local.mail = new mail();
// Set it's properties
local.mail.setSubject("Example Application");
local.mail.setTo(arguments.email);
local.mail.setFrom("noreply@example.com");
local.mail.setType("html");
// Send the email
local.mail.send(body = mailBody);
local.fnResults = {status : "200", message : "An email has been sent to <b>" & arguments.email & "</b> with further instructions."};
} else {
local.fnResults = {status : "400", message : "Error! Something went wrong."};
}
}else{
savecontent variable="mailBody"{
writeOutput('<br>We recieved a password reset request. The email you have provided does not exist in our system.<br>');
}
local.mail = new mail();
// Set it's properties
local.mail.setSubject("Example Application");
local.mail.setTo(arguments.email);
local.mail.setFrom("noreply@example.com");
local.mail.setType("html");
// Send the email
local.mail.send(body = mailBody);
local.fnResults = {status : "200", message : "An email has been sent to <b>" & arguments.email & "</b> with further instructions."};
}
然后下一步是,如果电子邮件存在并且用户单击链接,我将显示他们可以输入新密码的表单,或者他们将看到消息This link has expired or does not exist anymore.
。以下是Reset.cfm
页面的示例:
if (structKeyExists(url,"token") && isValid("uuid", url.token) && len(trim(url.token)) == 35){
cfstoredproc( procedure="CheckToken", datasource=dsn ) {
cfprocparam( maxlength=35, null=!len(trim(url.token)), cfsqltype="cf_sql_varchar", dbvarname="@Token", value=trim(url.token) );
cfprocresult( name="TokenResult" );
}
if( TokenResult.recordCount == 1 ){ //If token is valid (not expired) show the form.
<form name="frmRecovery" id="frmRecovery" autocomplete="off">
<input type="hidden" name="token" value="<cfoutput>#url.token#</cfoutput>">
<div class="form-group">
<div class="alert alert-info"><strong>Info!</strong> After saving your changes, you will be taken back to the login screen. Log into the system with the account credentials you have just saved.</div>
</div>
<div class="form-group">
<label class="control-label" for="password"><span class="label label-primary">Password</span></label>
<input type="password" class="form-control" name="frmRecovery_password" id="frmRecovery_password" placeholder="Enter Password" maxlength="64" required>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
<div class="form-group">
<div class="alert message-submit"></div>
</div>
</form>
}else{
<div class="alert alert-warning">
<strong>Warrning!</strong> This link has expired or does not exist anymore! </div>
}
}
如果用户被定向到表单以输入密码,我将保存新密码并删除令牌。下一步是将他们定向到登录页面,他们可以输入凭据和登录名。所以我的问题是,当管理员创建新帐户时,我可以使用类似的方法吗?管理员需要输入first
、last name
、username
等。然后单击按钮Send Email
该按钮将转发username
和临时链接,新用户可以在其中输入密码并在应用程序中登录。之前使用的逻辑是生成临时密码,用户登录,然后必须重置密码。我想知道我提出的解决方案是否具有任何安全风险,或者与带有临时密码的解决方案一样好?
您可以使用现有流程,因为您还有一个用于添加此处未包含的用户信息的过程。 此外,您没有指定管理员将提供一封电子邮件,这显然对发送令牌很重要(除非用户名是电子邮件(。
为了提高安全性,根据您想要的强化/安全性,一些建议:
GUID/UUID 旨在是唯一的,但肯定不是不可猜测的。 他们的目标是避免碰撞,而不是预测。 如果你输出一组由冷聚变生成的UUID,你可以在一定程度上猜测一些可能的组合,以便假装重置过程,直到你找到一个 - 假设你有一个有效的UUID,在几百万左右(很少(。
这就是这种攻击的工作方式 - 您将为两个用户同时发送两个重置请求。 一个是已知的(假设拥有电子邮件但权限较低的员工(和一个未知的(假设您无权访问其电子邮件的经理(。
您将为员工(攻击者(创建"种子"或已知的 UUID,与经理(受害者(的重置密钥大致同时创建。 然后,您可以根据已知的 UUID 为该 UUID 块创建重置尝试。 这样做不需要太多的处理能力或时间。
如果您改为创建一个 UUID 的复合字符串,并结合随机生成/存储的盐(这将是您希望为用户记录存储的附加字段( - 这将是最强的度量,因为它是唯一的并且无法用已知键猜测。
另一种是限制/限制对重置过程的请求(即 500 毫秒延迟等,用户看不到任何明显的差异,但严重限制了自动攻击的有用性(。
此外,您可以通过电子邮件或用户存储尝试,并锁定用户不尝试过多的重置请求,并需要人工干预/另一种更仔细的解锁路径。
当然,您的密码也应该用随机盐进行哈希处理。
不用说,要求强密码更安全。