如何为新用户帐户创建临时链接?

时间:2018-09-25 12:10:22

标签: authentication coldfusion passwords temporary generate

我创建了单页应用,该应用需要usernamepassword进行用户身份验证。系统中的每个帐户都是由管理员创建的。用户没有选择单击链接并创建帐户的选项。在研究并寻找最佳解决方案之后,我改变的第一件事是用户如何恢复密码。这是有关该逻辑如何工作的简短架构。用户首先必须单击链接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>
        }
    }

如果用户被引导到表单中输入密码,我将保存新密码并删除令牌。下一步是将他们定向到登录页面,他们可以输入凭据并登录。所以我的问题是,管理员创建新帐户时可以使用类似的方法吗?管理员需要输入firstlast nameusername等,然后单击按钮Send Email,它将转发username和新用户可以输入的临时链接他们输入密码并登录应用程序。之前使用的逻辑是生成临时密码,用户登录后必须重新设置密码。我想知道我提出的解决方案是否会带来安全风险,或者与使用临时密码的解决方案一样好?

1 个答案:

答案 0 :(得分:1)

您还可以使用现有过程,因为您还可以使用此过程添加用户信息(此处未提供)。此外,您没有指定管理员会提供一封电子邮件,这显然对发送令牌很重要(除非用户名是电子邮件)。

要提高安全性,具体取决于您想要的加固/安全性,一些建议:

GUID / UUID是唯一的,但当然也并非不可猜测。他们的目标是避免碰撞,而不是预测。如果输出由Coldfusion生成的一组UUID,则可以在某种程度上猜测一些可能的组合,以便伪装重置过程,直到找到一个为止-假设您有一个有效的UUID,开始时在几百万左右(非常大)小)。

这就是这种攻击的工作方式-您将同时向两个用户发送两个重置请求。已知的一个(让拥有雇员但具有较低权限的雇员)和未知的(让拥有管理员但您无权访问其电子邮件的经理)。

您将在大约与经理(受害者)重置键相同的时间为雇员(攻击者)创建“种子”或已知UUID。然后,您可以根据已知的UUID为该UUID块创建重置尝试。这样做不需要太多的处理能力或时间。

如果您改为创建一个UUID的复合字符串,并结合一个随机生成/存储的盐(这是您要存储给用户记录的附加字段)。-这将是最强的措施唯一,无法用已知密钥猜测。

另一种方法是限制/限制对重置过程的请求(即500毫秒的延迟等,用户看不到任何明显的差异,但严重限制了自动攻击的作用)。

此外,您可以存储通过电子邮件或用户进行的尝试,并阻止用户尝试过多的重置请求,并且无需人工干预,也可以采用其他审查方式进行解锁。

当然,您的密码也应使用随机的盐来散列。

毋庸置疑,要求使用强密码会更安全。