Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SECURITY] Bad UX for lost password / password reset #5203

Open
jonom opened this issue Mar 18, 2016 · 32 comments
Open

[SECURITY] Bad UX for lost password / password reset #5203

jonom opened this issue Mar 18, 2016 · 32 comments

Comments

@jonom
Copy link
Contributor

jonom commented Mar 18, 2016

Upon successful reset of password through lost password email, ChangePasswordForm will redirect you to whatever location happens to be stored in Session::get('BackURL'). This may be desired in some cases but I think it leads to a a bad user experience on two counts

  1. Unpredictability about what page the user will end up on
  2. No feedback / success message for the user may leave them in doubt as to whether the password reset was successful

Think it would be better to ignore the BackURL and instead redirect people back to the change password form every time with a success message. Also think it's a bad idea to ever store a BackURL in Session with no context. If a user starts a process that causes this value to be set but doesn't finish it, then that value could be picked up much later and create an unpredictable/undesirable result, in the same process or a completely unrelated one.

Related: #3757 (As of SS4, the result of resetting your password and successfully logging in is an offer to log in as someone else, with no way to get back to the page you were trying to log in to.)

Edit - improvements being pitched:

  • Upon successful password change, drop instant redirect and replace with confirmation message step which includes a link to continue to the redirect destination.
    • Alternative idea: Redirect + Toast notification. Could work for /admin destinations but not other protected URLs
    • Alternative idea: Warning a user in the form that they're going to be redirected after the change. This is better than nothing but not as good as an explicit success message.
  • Ensure that BackURL, default_reset_password_dest, and default_login_dest are persisted and respected in this workflow.
@dhensby
Copy link
Contributor

dhensby commented Mar 18, 2016

Agreed. The entire password reset function is a bit of a pain from a user point of view

@Firesphere
Copy link
Contributor

I would not redirect back to the change password form. I personally hate to see the form again, after submitting. It somehow feels like I did something wrong.
Suggestion:
Create a "your password has been reset" page, with a text explaining what happened, and a link saying something like "Go back to where you were before resetting your password", which obviously would point to the BackURL.

@madmatt
Copy link
Member

madmatt commented Mar 25, 2016

If the ChangePasswordForm set a ChangePasswordBackURL the first time it's seen (when it knows the referrer), and redirects them back when it's successfully changed.

The only issue is that it wouldn't always work (e.g. this form is used when the user's password has expired, which is set whenever the user logs in and their password has expired, so you can't take them back to the login form as that's pointless too.

@jonom
Copy link
Contributor Author

jonom commented Mar 30, 2016

I would not redirect back to the change password form. I personally hate to see the form again, after submitting.

I'm not attached to showing them the form again, the important thing to me is that we give users a success message, not blindly throw them to some location they may or may not want to go to with no indication of whether or not the password change was successful.

@dhensby do you have any thoughts about SemVer on this one? I'd like to fix this in 3.x but is this a broken enough behaviour that we can stop redirecting people and not consider it a breaking change? I guess it could be an opt-in fix with a config variable but seems convoluted.

@jonom
Copy link
Contributor Author

jonom commented Apr 5, 2016

So I whipped up a change that redirects users back to the form (for now, because it's easy) and displays a confirmation message, but also shows a link to the BackURL if available, which I thought might be a nice compromise.

But then I wondered if this could be a security vulnerability, because the BackURL is posted in via the form so that could be manipulated and maybe cause this page to display some undesirable HTML instead of a valid URL?

screen shot 2016-03-31 at 2 03 03 pm

@tractorcow
Copy link
Contributor

As long as you validate that it's a site url, and it's properly encoded, then it's fine.

@dhensby
Copy link
Contributor

dhensby commented Apr 6, 2016

@jonom you could make the form redirect to that URL after x seconds (if it is valid).

Validate URLs by doing Director::is_site_url()

@dhensby
Copy link
Contributor

dhensby commented Apr 6, 2016

In terms of SemVer: I think the best process here is to use deduction..

is it a patch: No
is it a new feature: yes
is it a breaking change: maybe?

So, it's definitely not the 3.3 branch, it might be 3 branch if we think that this doesn't "break" an API

I think it's reasonable that a unit test would assert that this step moves a user on to the BackURL, so I think, unfortunately, this is a 4.x change.

What we could do is add this to 3.x with a config option to turn it on and then just enable it in 4

If you PR against 4, please add it to the changelog/upgrade notes

@jonom
Copy link
Contributor Author

jonom commented Apr 6, 2016

There's already a check against Director::is_site_url() but I don't think that's sufficient at all... it falls back on this function:

public static function is_relative_url($url) {
    return (!Director::is_absolute_url($url));
}

So it looks to me like you can pass any string at all to Director::is_site_url(), and if it's not specifically detected as an external URL, you'll get a 👍. It's not validating that this is actually a URL we're dealing with and safe for attribute insertion.

Yeah I had a feeling it would be a 4 thing. I'm not sure it's worth doing an opt-in change for 3.

@Firesphere is this overlapping with work you're already doing? I might leave it to you if it is so we're not double handling this issue. have left some thoughts in your dev list post.

@dhensby
Copy link
Contributor

dhensby commented Apr 8, 2016

It's not validating that this is actually a URL we're dealing with and safe for attribute insertion.

I guess that comes down to casting in the template, then?

@jonom
Copy link
Contributor Author

jonom commented Apr 11, 2016

Yeah I guess I would just have to make sure it ends up getting escaped properly and still works properly as a link. Probably a way easier problem than I've made it out to be in my head.

I'm going to pause exploring this until I see what @Firesphere comes up with. @dhensby feel free to close this issue if you see it as more of a feature request than a bug.

@Firesphere
Copy link
Contributor

You're free to comment and/or help. I've created a separate repository, with a framework version minus security (almost).
And a separate Security module here: https://github.com/Firesphere/silverstripe-security

My current implementation, is showing a text saying password is changed and the user is logged in, and can follow a link to go back wherever they came from (backurl from session)

Also done massive restructuring/refactoring, so feedback on there is very welcome 👍

I added ZenHub to the Security repository. I don't know if it'll work out, I just wanted to try it.

@dhensby
Copy link
Contributor

dhensby commented Apr 12, 2016

@Firesphere is that going through an RFC process? Does "Security" include Members?

If security is pulled out, wouldn't the whole admin have to be pulled out too?

@jonom
Copy link
Contributor Author

jonom commented Apr 12, 2016

Pulling Security out doesn't quite sounds feasible so interested to see how it turns out. Does seem like RFC territory, but I have to say based on RFC-2 that if I'd put my energy in to building a proof of concept I could demonstrate instead of writing and defending a theoretical proposal it would probably be all done ages ago. So it's probably a good idea to do some experimenting first but then maybe write an RFC once you're confident about the general form your solution will take?

@Firesphere
Copy link
Contributor

@dhensby I am not pulling it out really. I separated it to make it easier to work on and keep track of issues etc. not to be a separate module.

Once done, it can be put back in quite easily.

@dhensby
Copy link
Contributor

dhensby commented May 12, 2016

ah, ok

@sminnee sminnee changed the title Bad UX for lost password / password reset [SECURITY] Bad UX for lost password / password reset Oct 6, 2018
@maxime-rainville
Copy link
Contributor

maxime-rainville commented Aug 10, 2020

The password reset form has substantially changed since we introduced the MFA module which now ships by default with the installer. I've recorded what it looks like: https://youtu.be/V1woJiKHaWk

I guess we could have a toast notification when you successfully access the CMS after changing your password. But otherwise the flow looks pretty good to me.

@silverstripeux Maybe have a glance at the video and make call on if it's something you would like improved.

@Firesphere
Copy link
Contributor

Please, no toast notification, they're useless to the end user, it doesn't help them at all, because it's a tiny notification in the corner of the screen, a proper notification is a lot more useful.

@clarkepaul
Copy link
Contributor

For the most part the experience seems good. Yup, missing some sort of notification, but I would think a toast notification is ideal in this situation. The user already knows its worked as they have entered the CMS interface. Toast notifications are used when things happen as the user would expect, e.g. continue, everything is good. I think this is a good example of what they have been designed for IMHO.

@jonom
Copy link
Contributor Author

jonom commented Aug 13, 2020

I watched the video (thanks @maxime-rainville), it certainly looks prettier than before but I'm not sure it anything has changed apart from a re-skin? No idea if my thoughts on Session::get('BackURL') are relevant today (or were 4 years ago) but I'd personally still like to see some kind of explicit confirmation that the password change was successful.

What happens if you were trying to access a restricted page rather than the CMS? Do you still get redirected there immediately? Depending on the design of the site I'd still argue that users might be left unsure whether the password change worked in some circumstances (particularly since a toast notification not really an option in that context out-of-the-box), so I think my original suggestion of showing a success message with a link you can click to go to the location you were trying to log in to (rather than redirecting immediately) is still a decent idea.

@maxime-rainville
Copy link
Contributor

If you're trying to access a restricted page, it seems you still get redirected to that page after resetting your password. That's what it looks like. https://youtu.be/FjgovT_iwX8

@jonom
Copy link
Contributor Author

jonom commented Aug 13, 2020

@maxime-rainville it's not terrible but if we kept the redirect behaviour I think it would be nice if the password reset form at least told you what was going to happen. Like 'Please enter a new password' could become 'Enter a new password, then we'll take you straight to /admin.'

BTW should that password reset email have some more meat to it? Here's an example from Slack:

Screen Shot 2020-08-12 at 9 35 50 pm

@sminnee mentioned the design aspect, would be great to see that cleaned up.

@clarkepaul
Copy link
Contributor

Being lazy I didn't read the all the threads... having a success message prior to moving into the CMS also works.

@jonom
Copy link
Contributor Author

jonom commented Aug 31, 2020

I left a comment very relevant to this issue here: #3757 (comment). TLDR; The result of resetting your password and successfully logging in is an offer to log in as someone else, with no way to get back to the page you were trying to log in to, or the default_login_dest as set by the developer. That sucks a lot.

Screen Shot 2020-08-31 at 12 07 24 pm

@clarkepaul
Copy link
Contributor

Related silverstripe/silverstripe-admin#1066

@Firesphere
Copy link
Contributor

'Enter a new password, then we'll take you straight to /admin.'

That literally gives away unwanted information. I would personally not like it if I'd see something like that.

It would work for non-admin URLs, but for admin URLs, I would not like to see that.

@jonom
Copy link
Contributor Author

jonom commented Sep 1, 2020

gives away unwanted information

@Firesphere Can you clarify what you mean by unwanted information - do you mean that it's cryptic / unhelpful? I just think that if we keep the immediate redirect behaviour (no success message displayed to user) then we should inform the user that that is going to happen.

My preferred UX which I outlined here (sorry for the fragmentation) would be to not immediately redirect the user and instead display a success message on a new action like Security/passwordchanged. Then instead of displaying the exact location we could just say 'Continue' or similar vague action on a link that take you to whichever is applicable of BackURL, default_reset_password_dest or default_login_dest.

Example:

Screen Shot 2020-08-31 at 7 44 22 pm

@Firesphere
Copy link
Contributor

@Firesphere Can you clarify what you mean by unwanted information - do you mean that it's cryptic / unhelpful? I just think that if we keep the immediate redirect behaviour (no success message displayed to user) then we should inform the user that that is going to happen.

It gives information that's not wanted, e.g. giving away the next endpoint

@jonom
Copy link
Contributor Author

jonom commented Sep 1, 2020

Is it sensitive information? I guess I was focused on BackURL and assumed we'd just be displaying the URL they were trying to access in the first place (i.e. already known to them). But I suppose that wouldn't always be the case. Maybe it could just say something like "Enter a new password, then we'll take you straight to your destination" instead (if we stick with the redirect behaviour).

@robbieaverill
Copy link
Contributor

Yeah so putting /admin in the message is a low level security vulnerability in that you're disclosing the admin URL. Our implementation of a configurable admin URL is half baked and doesn't actually work, but we should still aim not to perpetuate these issues.

We can say 'Enter a new password, then we'll take you straight to the CMS.' (or "the admin area" if the CMS module isn't installed), then we're avoiding that problem.

@maxime-rainville
Copy link
Contributor

Just discovered there is a Security::$default_reset_password_dest config setting which does work. I still think that showing a success message at /Security/passwordchanged is a good idea, and the Continue button should take you to whichever is not null in this order:

  1. BackURL
  2. default_reset_password_dest
  3. default_login_dest

Edit: oops messed up the order, BackURL should be first 😅

#3757 (comment)

@jonom
Copy link
Contributor Author

jonom commented Sep 3, 2020

Yeah so putting /admin in the message is a low level security vulnerability in that you're disclosing the admin URL

Yeah I was assuming that the destination (could be /admin or any other restricted page or URL) was already known to the user because typically you end up at a login screen because you've tried to access that URL. E.g. if you're trying to access the CMS, the workflow would be user tries to access /admin -> user is redirected to /Security/login.

We can say 'Enter a new password, then we'll take you straight to the CMS.' (or "the admin area" if the CMS module isn't installed), then we're avoiding that problem.

I would rather have a 'friendly name' than a URL (e.g. "the CMS" as you suggested) but a restricted URL on a SilverStripe app could be anything and isn't necessarily going to be easily resolvable to a nice name as is the case with the CMS. Also, if you did give away the name then presumably that's a form of the same security vulnerability being discussed - namely revealing that the endpoint does exist (and by the way, here's a friendly title for it...)

Ultimately for simplicity probably just saying something vague like "we'll take you straight to the URL you were trying to access" (preparing users to expect a redirect) would be fine. It's a moot point though if we do the better fix which (as @Firesphere originally suggested) is to ditch the redirect and instead display a confirmation message with a link to whatever the redirect destination would have been.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants