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

Prevent password reset token leak via HTTP referer #706

Closed
wants to merge 1 commit into
base: master
from

Conversation

Projects
None yet
3 participants
@derekprior
Copy link
Contributor

derekprior commented Sep 23, 2016

The password reset token is included in the URL emailed to users who
request a password reset. When the user clicks the link, they are
brought to the password reset form. If instead of completing the form
the user clicks a link to another HTTPS resource that may be in the site
navigation, then the password reset token will be leaked to that
external site via HTTP referer (sic).

Taking advantage of this would require an attack to:

  1. Control a site that is linked to on the password reset token
  2. Capture any HTTP referers that contain the token
  3. Use the referer URL to complete the password reset before the user
    does so themselves.

This is a rather involved exploit, but something we should close
nonetheless. We address it in this change by rotating the password reset
token on the edit page. This will cause the displayed form to submit
with the new, updated password reset token. The token in the form will
be valid, while the token in the URL is now invalid. If the URL token is
leaked, it will be useless to an attacker.

This is the simplest change I could think of to fix this in a likely
backward compatible way. If developers have overridden
Clearance::Passwords#edit without calling super, they will have to
implement the same fix locally.

This change could impact end users who click the reset link and for
whatever reason do not complete the reset. If they were to use that same
link again, it would now be invalid. This is consistent with other
password reset systems that expire tokens immediately or with a short
timeout.

Prevent password reset token leak via HTTP referer
The password reset token is included in the URL emailed to users who
request a password reset. When the user clicks the link, they are
brought to the password reset form. If instead of completing the form
the user clicks a link to another HTTPS resource that may be in the site
navigation, then the password reset token will be leaked to that
external site via HTTP referer (sic).

Taking advantage of this would require an attack to:

1. Control a site that is linked to on the password reset token
2. Capture any HTTP referers that contain the token
3. Use the referer URL to complete the password reset before the user
does so themselves.

This is a rather involved exploit, but something we should close
nonetheless. We address it in this change by rotating the password reset
token on the edit page. This will cause the displayed form to submit
with the new, updated password reset token. The token in the form will
be valid, while the token in the URL is now invalid. If the URL token is
leaked, it will be useless to an attacker.

This is the simplest change I could think of to fix this in a likely
backward compatible way. If developers have overridden
`Clearance::Passwords#edit` without calling `super`, they will have to
implement the same fix locally.

This change could impact end users who click the reset link and for
whatever reason do not complete the reset. If they were to use that same
link again, it would now be invalid. This is consistent with other
password reset systems that expire tokens immediately or with a short
timeout.
@mike-burns

This comment has been minimized.

Copy link
Member

mike-burns commented Sep 23, 2016

What happens when the user reloads the page?

@derekprior

This comment has been minimized.

Copy link
Contributor Author

derekprior commented Sep 23, 2016

Think I'm going to go with the session approach after all.

@derekprior derekprior closed this Sep 23, 2016

@exalted

This comment has been minimized.

Copy link

exalted commented Oct 24, 2016

Hi @derekprior I listened to the podcast episodes back then, and just finished reading the blog post about this issue. It was a nice conversation, and I am glad you came up with a solution. 👏

I wonder whether you considered a CSRF-like approach, in which when the user requests to reset their password, the website issues a request validation token and store it in user's cookies. When the user clicks on the link contained in the e-mail, however, the password reset form submit action is accepted only if validation token is valid (or maybe they don't even get to see the form page).

Since the validation token would only be in user's browser, I think it wouldn't be possible for a malicious party to exploit possibly leaking Referer.

What do you think?

@derekprior

This comment has been minimized.

Copy link
Contributor Author

derekprior commented Oct 24, 2016

I hadn't considered that. It's an interesting idea. The only downside I can think of is that it means the user must use the same device and browser to request and complete the reset.

That's not a deal breaker for me but I think it's different enough from other reset mechanisms that it might lead to some confusion.

Are you aware of major sites or libraries that do that?

@exalted

This comment has been minimized.

Copy link

exalted commented Oct 24, 2016

Not really, no. However, in my own usage, I have ever noticed anybody use the reset mechanism to open the web page from a different device/browser.

I think it is reasonably safe to assume that the actual password reset will happen in a minute or so (you usually need the password right now, else why are you asking for a reset anyway?):

  1. ask for a reset
  2. click on the link in the email
  3. a new tab in the same default (same?) browser will open (even possibly the exact same page could reload)
  4. fill the form and submit

I think we both agree that "security by obscurity" (that what most password resets do) is the real problem here. So a same-origin-like validation token could help a bunch.

@exalted

This comment has been minimized.

Copy link

exalted commented Oct 24, 2016

Not entirely sure, but I have a feeling that Django does check for the token. I may have misread all that, just ignore, please. 😞

@derekprior

This comment has been minimized.

Copy link
Contributor Author

derekprior commented Oct 24, 2016

I have asked for a reset and ultimately completed the reset on my phone, or vice versa. I have also requested a reset from Chrome (a browser I occasionally use for Flash), and completed the reset in Safari (my main browser I have my email open in all the time).

Those may be edge-casey enough though. This isn't a bad solution, I'm just not sure of its impact usability wise.

@exalted

This comment has been minimized.

Copy link

exalted commented Oct 25, 2016

I have asked for a reset and ultimately completed the reset on my phone, or vice versa. I have also requested a reset from Chrome (a browser I occasionally use for Flash), and completed the reset in Safari (my main browser I have my email open in all the time).

Haha, that is very interesting, and definitely is something that has never occurred to me.

Those may be edge-casey enough though. This isn't a bad solution, I'm just not sure of its impact usability wise.

Fair enough. The solution that I was suggesting has the upside of being "secure" at the cost of edge-casey bad UX issues.

The currently implemented solution of invalidating reset token as soon as the user clicks on the link has the upside of being cautious not using the same token twice; but the downside is, although very unlikely, the first person to hit that URL may not be the user themselves. Essentially it's "first-come, first-served". That's bad UX as well.

I don't have any data to backup however which of the edge cases are more unlikely to happen. 😞

Here's another idea: why not asking for the user's email address again in the final password reset page? If the link with the reset token is leaked, chances are, only the user knows their own email, and not the third-parties.

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