Settings

Theme

Is Your Site Leaking Password Reset Links?

robots.thoughtbot.com

174 points by andrenarchy 9 years ago · 92 comments

Reader

andrenarchyOP 9 years ago

If you use javascript to extract the token from the URL then you can simply pass it via the hash ("fragment") part of the URL. The hash portion is only interpreted by the user agent and never sent to a server (see https://tools.ietf.org/html/rfc3986#section-3.5). This is how we solved it at paperhive.org.

  • derekprior 9 years ago

    This has been suggested numerous time since I published. I had not previously considered this solution and I think it's a fine solution for people to make if they know the tradeoffs.

    It's worth noting there are a number of reasons this JavaScript could possibly not execute beyond people who have JS turned off. I've seen a number of sites fail to execute JavaScript when an Ad Blocker is run, for instance.

    In this case, there are a number of server side fixes available that wouldn't require any JavaScript. They're not terribly complicated and will always work. For that reason, I'm still comfortable with the server side fix, but think the JS fix is a decent alternative.

  • oneeyedpigeon 9 years ago

    Obviously, the drawback is that you've introduced a javascript dependency to a core function which definitely doesn't require it. Having said that, I notice that paperhive.org renders an entirely blank page if javascript is unavailable, so I guess the password reset is the least of your concerns in that scenario.

    • angry-hacker 9 years ago

      And what percentage of the users have javascript disabled? Objectively you have bigger concerns when you run a site than 10 people who have js disabled.

      • oneeyedpigeon 9 years ago

        Definitely more than zero. It's not just a case of javascript being disabled, either - there are many other reasons why it might be "unavailable", which is why I used that word.

        Of course I'm not suggesting you're not allowed to use any javascript on your site, or even that you should only use it when it's strictly necessary, but if it's entirely unnecessary and you don't engage in best practise by, for example, using progressive enhancement, then that's something that could be improved.

      • tomjen3 9 years ago

        I used to think so, but information sites probably needs to work without javascript, if only for google bot.

    • VertexRed 9 years ago

      It's 2016, everyone has JS enabled.

      The ones that don't are most likely bots (now even that's changing thanks to projects like phantomjs).

      • dmm 9 years ago

        >everyone has JS enabled.

        Using an addon like NoScript it's possible to selectively enable javascript per domain. When a website doesn't work without js I am forced to decide whether it's worth enabling js for this site. Very often I decide it's not worth it and I never use that site again.

        • spdustin 9 years ago

          You and I both. Remember, however, that you and I are not a representative sample of the general population of web users.

          • andrewflnr 9 years ago

            That would be meaningful if we were talking about some optional UX feature, but this is security. Does having a non-representative browser config mean we don't deserve security? I think not. Security has to work without JS.

        • zeveb 9 years ago

          And I hate it when a site is loading JavaScript from a large domain, e.g. cloudflare.com; generally I just close my browser window and view something else.

          No way am I going to allow JavaScript from every single site using CloudFlare to run all at once.

        • grimmdude 9 years ago

          Can I ask your reasoning in doing this?

          • mikegerwitz 9 years ago

            When your browser runs JavaScript, it downloading and automatically executing untrusted, unsigned, ephemeral code. Even if the site is over SSL, only the _party_ is validated---the resources themselves are not signed.

            If your browser instead presented the JavaScript as a program itself, and listed the programs it executed, and from what sources, users would have a wholly different perspective. JavaScript has the illusion of remote execution; most users don't think of it as executing programs on their computer.

            Addons like NoScript are essential security precautions that mitigate a host of attacks. Unfortunately, even security-essential software like the Tor Browser Bundle leaves JS enabled by default because it'd "break" the web.

            There's other reasons---as a free software user and activist, I won't run non-free JavaScript programs.

            I gave a talk earlier this year about these problems and some ideas to solve them: https://media.libreplanet.org/u/libreplanet/collection/resto...

          • marklgr 9 years ago

            Why should we trust any website and execute their JS code on our machine? What about privacy, if they decide they can track us and sell the information to whoever they want? And even if they're "legit", what about the 3rd parties they might trust wrongly?

          • theandrewbailey 9 years ago

            I come to a website to read it, not for it (and who knows what else) to execute code on my machine, no matter how deep into the sandbox it is. If I want to watch the video and allow it to use my data, I will explicitly allow it.

          • derivagral 9 years ago

            I personally do this for some combination of privacy, security, and ad blocking.

          • tomjen3 9 years ago

            Gets around all the anti-ad-block websites and it is much, much, much faster.

            Sadly doesn't work in Chrome.

  • vbezhenar 9 years ago

    For users without JavaScript it's reasonable to include a plain form and send token in plain text as well, so user can copy-paste it into a form and reset his password.

  • mkagenius 9 years ago

    You are assuming that 3rd party javascript is not taking the whole URL including the hash part and sending it for analytics. I have seen at least one analytics company do that.

  • morgante 9 years ago

    Wouldn't any (third-party) JavaScript on the page still have access to it?

    • fredsted 9 years ago

      They would also have access to everything else, including the entered password.

      • morgante 9 years ago

        Right. The scenario I'm considering is not a hostile attacker but simple an overzealous analytics script which also tracked hash strings. Heck, since some sites used to do AJAX navigation that way it wouldn't surprise me if some analytics services were configured to track hash strings.

tvelichkov 9 years ago

If you steal someones else password-reset link, change the password, then at the end of the day, won't you end up with a password, but missing email/username in order to log in? I mean the reset password link shouldn't reveal any other credentials about the account. (I know at some sites after reseting a password you may end up automatically logged in, but i think this is a bad practice).

  • derekprior 9 years ago

    Depends on how the reset behaves. Some resets log you in immediately after providing a new password. Some require you to log in after resetting the password.

    I feel like I've seen more of the former than the latter.

  • nchelluri 9 years ago

    I don't think it's so simple.

    Before generating the PW reset link, someone might try to login first. So they'd enter bob@example.com into the login form and then when that failed, it's not uncommon to redirect with error-msg-in-session to /login?email=bob@example.com . So you'd leak the email first.

  • milankragujevic 9 years ago

    Given how many (more than 90% imo) reset links are something like /reset?email=test@test.com&token=0123456789, that's not quite an issue as it may seem...

jpalomaki 9 years ago

One option is to avoid putting the token to the link and at least provide user a simple way of copy-pasting the token to the password reset form.

Sometimes this is actually something I as user want to have, since it might be that I'm receiving the email on device A, but want to reset the password on device B.

And please keep the password reset tokens sane. If you are not encoding some data into the token, you don't really need that 80 character random string for security.

  • derekprior 9 years ago

    I'd certainly consider this for some applications. It depends on the value of what you are protecting. For some sites and users, the error rate introduced by this method would be unacceptable.

    Tradeoffs...

  • tinus_hn 9 years ago

    Many people do not understand copy and paste at all.

moloch 9 years ago

This is bad but not horrible, especially in the example given leaking the reset token to Cloudfront. The application is loading JavaScript from the Cloudfront origin, so that origin by definition could already read the tokens by modifying the JavaScript (assuming no SRI). The request is sent over SSL/TLS so the token cannot be viewed by a MITM, and referers aren't sent across HTTP<->HTTPS transitions.

Again this is far from ideal, but also not readily exploitable by attackers that couldn't already access the data.

  • derekprior 9 years ago

    Author here. I completely agree that it's unlikely to be exploited but also think fixes are mostly simple enough that it should be addressed.

darkhorn 9 years ago

What I do is I start a session on password reset page. I send an email with the reset link. User visits the link. The web page checks wheter the requester is same with the session identifier. Only then the user has a right to create a new password. So, in other words, if the user tries to visit the link with a different browser a warning says "use your browser that you used to reset your password".

  • initram 9 years ago

    So if I'm in a private session and clicking the link in my email opens a new page in a different session, then I can't reset my password? That's lame.

    I've even had a situation where I was on my desktop machine and clicked a reset link on the web site. I realized I didn't have my email set up on that machine yet, so I went to my phone and did it from there. In your scenario, this wouldn't work. That seems problematic.

    • darkhorn 9 years ago

      If you are on a private session you can copy the link to a new private tab and it will work.

      My web site's visitors are only from my universty. Only to those who have METU email addresses. It is easy to log in to a web mail from the browser. Password reset is not a something done on daily bases. It is okay for my situation. Not very user friendly but it is a bit more secure.

      In fact the idea come from this; what if a student fills registration form and sends the validation email to his teavher. And the teachet, without reading click, in other words validates the registration process mistekenly. Now, I have a criminal case (I shouldn't allow Professor Naughty Elizabeth to be registeted for example) against me! I wanted to protect my ass. And I used it too in password resets.

zaroth 9 years ago

Referrer leaks are such a foot-shoot. There should have been a piece of the URL which never touched the Referrer but also was submitted to the server from the get-go. But obviously an easy edge case to miss in 1991 - 1996 [1] [2].

But this is sad: http://caniuse.com/#feat=referrer-policy

[1] - http://www.w3.org/pub/WWW/Protocols/HTTP/AsImplemented.html [2] - https://tools.ietf.org/html/rfc1945

kelvin0 9 years ago

I've read the article twice, and I'm not totally clear as to what the problem is, and how it's being addressed (web n00b here). Also, this bit:

"Browsers will not send the Referer for resources fetched via HTTP from a document loaded via HTTPS"

So using HTTPS resolves the issue? But breaks analytics?

Any further clarification would be nice, thanks!

  • msimpson 9 years ago

    The article is simply pointing out that services which record referrers can inadvertently store live password reset tokens if you're not careful.

    For instance, someone places Google Analytics in the head of the default layout for a given site. Now traffic to and from the password reset page, which uses that layout, is being recorded. This means an attacker would only need to gain access to that account, which is probably much less guarded, and gather referrers containing password reset tokens. From there they could quickly try the last few--which might still be active--and easily gain access to one or more accounts within the site.

    • kelvin0 9 years ago

      >>...would only need to gain access to that account, which is probably much less guarded..

      So "that account" you mention, is it the Google analytics account or the web application (leaking urls) account? Why is it much less guarded?

      • msimpson 9 years ago

        I mean the analytics account; and in my experience it is less guarded due to clients handing out access as it's "just reporting" in their eyes.

  • mankyd 9 years ago

    The basic observation is that the reset url is sent as the referrer to any 3rd party content that gets loaded on the page. So if you have a facebook link, ads, analytics, cdn, or other random content on the page, they get to see what the reset url is.

    This is a valid concern, though it's worth noting that you shouldn't be including 3rd party content you don't trust on any potentially sensitive page in the first place. This includes not only password pages, but potentially ANY logged in page on the site.

    Otherwise, you should consider the 3rd party content trusted and move on.

  • jordanlev 9 years ago

    The thing about https only applies when an https page is loading assets from a non-http address. But if your site uses https and so does your CDN, then the referer headers are still sent.

  • impostervt 9 years ago

    If you're site uses a password reset token email as the article describes, and your Reset Password page loads 3rd party scripts or css, those 3rd parties (and any servers en route to them) may be able to see the password reset token as part of the HTTP Referer header.

eterm 9 years ago

For analytics this isn't a big problem.

Once a reset link has been clicked, it should be immediately invalidated.

So unless the server was able to respond to the link and provide the analytics stuff but not somehow invalidate the token, I can't see how this is a problem.

Another related problem is that some third party mailers move all their links via URL redirectors. In that case there's a chance the host application fails and the link is left valid.

  • fahrradflucht 9 years ago

    >Once a reset link has been clicked, it should be immediately invalidated.

    I'm not sure about this... Couldn't this produce some unexpected reset failures in cases of browsers preloading links in webmail clients?

    • mgbmtl 9 years ago

      Drupal (and WordPress, if I recall correctly) invalidate immediately. Considering reset links are sent in plain text by email, it's a good way to test whether the link has been used by someone else.

      Besides, it would be an odd security hazard if browsers/webmails preloaded links in emails (malicious URLs in spam/scams).

      edit: in Drupal, the reset link loads a page with a button that the user must click on. This avoids issues with potential preload or anti-virus scans.

      • zaroth 9 years ago

        What if they click the link but don't click the button? Do they need another password reset token at that point? If not, is there still an attack window there that needs to be plugged?

        • mgbmtl 9 years ago

          The links usually expire after 24 hours (attack window), if unused.

          Loading the page without clicking does not invalidate the link.

          I agree this is not highly secure. It's basically one notch above sending cleartext passwords by email (which many websites unfortunately still do).

Normal_gaussian 9 years ago

So the fragment solution requires JS, invalidate on request can be upset by email scanning antivirus, manual token entry is inconvenient.

So my first thought is returning a page with no external requests (render on the srrver) which isn't very dev friendly.

My other thought is returning a redirect to a page with a token derived from the original token and the clients IP or some other fingerprinting information.

  • kijin 9 years ago

    Redirect is the cleanest solution.

    Most frameworks provide some form of storage (whether server-side or client-side) that is tied to a specific session, so you can use that to remember the fact that the current session recently used a valid password-reset token for user ID 123.

    Or you could use a similar mechanism to put the token in a hidden <input> after redirection, so that it gets submitted again when the user types in their new password.

    • bahjoite 9 years ago

      >Redirect is the cleanest solution.

      Exactly. Submitting tokens via GET requests (as is necessary for an emailed token) should be handled in the same way as POST (POST-redirect-GET): the resource which validates the token should not be the one that presents the "password reset" form.

  • richardwhiuk 9 years ago

    Redirect to a page setting a cookie with the token in, which is then scoped to the domain via Site Origin policies.

r1ch 9 years ago

You can also use the meta referrer tag on modern browsers.

https://moz.com/blog/meta-referrer-tag

  • derekprior 9 years ago

    It's covered in the article, but this is not supported by IE11. That's pretty modern...

zokier 9 years ago

Would putting the token to url fragment id resolve the issue? I suppose the downside would be that then js is required on the reset page. But that is fairly minor imho.

marichards 9 years ago

British regulators have highlighted this problem too. https://iconewsblog.wordpress.com/2015/09/16/does-your-websi... I wish someone would sue Mozilla, Google, Microsoft and Apple for data protection violations for supporting referer

milankragujevic 9 years ago

Does anyone have a list of common vulnerabilities that you should check your app against, maybe excluding the obvious ones like SQL Injection, XSS, etc... ? Because I can't keep track of all the vulnerabilities that exist in the world :(

pacifika 9 years ago

The problem is that your password reset token shouldn't be reusable. The token is only leaked after the person visited the reset page, which should invalidate the token on load.

Without a valid token, the reset form shouldn't load. Problem solved.

  • derekprior 9 years ago

    This requires non-idempotent get requests as you must invalidate the token on get.

    I did consider this approach for Clearance and intended to go with it, but was discouraged from doing so after hearing reports that some enterprise email AV does things like open some links in emails.

    There is also the user experience concern that a click the link in my email, do something else, then click the link again, having forgotten I already clicked the link. Now I'd have to re-request again.

    Also, this approach is impossible if you use HMAC tokens.

    I don't think anyone who opts for this approach is wrong but like most things, it's a tradeoff.

    • nchelluri 9 years ago

      Thanks for the additional thought process here; just a minor question, what's an HMAC token?

    • anton_gogolev 9 years ago

      > This requires non-idempotent get requests as you must invalidate the token on get.

      I think you should invalidate a token whenever a user enters a new password and submits a form (hence POST).

      • oneeyedpigeon 9 years ago

        That should definitely happen anyway but, as the article points out, that leaves a window of time between the user clicking the link in their email and them completing the form. It might be a very brief window, but it's still exploitable (and it won't always be brief - consider a user clicking the link in their email, leaving their desk for 5 minutes or going to make a cup of tea...)

        • Klathmon 9 years ago

          Not to mention the users that will request a reset but then remember, or forget to follow up with the reset (which I myself have been guilty of in the past).

  • nileshtrivedi 9 years ago

    How to implement this if the token is not stored in database at all (eg. JWT)? As far as I know, enforcing one-time-use only requires storing a bit in backend.

    • anton_gogolev 9 years ago

      If you could somehow encode the value of "PasswordLastUpdatedAt" in your token, you will then could have truly "stateless" tokens.

  • jordanlev 9 years ago

    Hence one of the proposed solutions at the end of the article is to generate a new token when the link is used and put the new token into the form.

    But some sort of token needs to be used even after clicking the email link because the "enter a new password" form needs it posted as well (to prevent people from using that form willy-nilly on any account).

garglgarbl 9 years ago

I'm a bit of a web noob as well, so I have a related question: If I have an https url with a token will the token only be sent through the https connection or is it contained in any lookups or connection metadata or such?

  • briHass 9 years ago

    Query parameters are encrypted if you are using HTTPS. The domain/host name (e.g. news.ycombinator.com) obviously is not protected, since the DNS lookup is required and the server (resolved IP) may host multiple sites. Seeing the hostname plaintext in the request is required for the server to disambiguate.

    So, for single-use tokens, you're probably OK for passing it in the URL (e.g. myhost.test/resetpw?token=abcdef), but it is usually considered a bad idea to use the URL for non-single-use secret info. Once it hits the server, the full URL may be stored in log files unsecured or if you use SSL termination before the server, it could be logged in other places as well. Additionally, the user's browser itself may store your secret URL in the history.

    • derekprior 9 years ago

      In my experience, password reset tokens are not single use. They are good for both loading the form and submitting the form. They are not invalidated until the form is submitted with the new password.

      They are good for 1 password reset, not 1 page load. It's possible to make them good for 1 page load, but most I've encountered are not due to the tradeoffs that would involve (see other discussions).

zeveb 9 years ago

He should have also considered the option of not loading third-party assets and analytics on password reset pages.

I'm grumpy enough to suggest not loading them period, but I'm a curmudgeon.

Lord_Zero 9 years ago

Can anyone check if https://stormpath.com/ has this issue?

jccc 9 years ago

Anyone here happen to know how Basecamp works? They seem to be comfortable with emailed login links, no username or password.

agotterer 9 years ago

Why not have a URL that identifies the reset request uniquely but also requires a code that is entered manually?

  • oneeyedpigeon 9 years ago

    In my experience, that really confuses users. Some just do not understand or read the email too quickly; they click the link, then they get stuck. If everything just works as a result of clicking a link, users are satisfied.

    BTW, why would the URL that identifies the reset request need to be unique if the tokens have enough entropy?

    • agotterer 9 years ago

      Going to need a whole lot of characters to prevent someone being malicious and just resetting passwords randomly. Long characters are also more annoying to type for the end user. If the link is unique you can keep the passcode a little shorter and it requires two pieces of information to complete a reset.

  • sbjustin 9 years ago

    Users are finicky (I am) :) That adds more work to it.

    • agotterer 9 years ago

      Yet plenty of successful companies do the equivalent with SMS codes during registration or account verification. It's not unreasonable that during a password reset you have to put in a few characters of extra work. If you're too lazy to type a few extra characters the account clearly isn't all that valuable to you.

homakov 9 years ago

There's absolutely no situation when it's a risky behavior. It is not a problem at all.

  • derekprior 9 years ago

    I think people are looking to closely at the first degree attack -- a trusted partner is pwned. If this is the case, there's far more interesting things an attacker could do. It's not too hard to envision a scenario where an attacker does not have access to embeded assets, but does have access to logs.

    1. You include a script such a TypeKit. The typekit deliverable itself is not owned, but bad actors have access to typekit.com logs.

    2. You use a smaller third party add on service that itself uses a logging service such as PaperTrail. PaperTrail is hacked, providing attackers access to logs.

    3. You reference no external assets, but your site contains external links in the footer. Users click the navigation links rather than completing the form. You have leaked the token to whatever site that is. You are at the mercy of their log storage. YES, this does actually happen. User's click crazy things.

    As I mention in the article and in other comments here: this is not likely to be exploited. Fixes, however, are not too difficult. Even adding the not-quiet-fully-supported `meta` tag to your head is a good start.

    If I read this article today, I'd think, "That's interesting. let me make a note to check that out." It's not a hair-on-fire security situation, but it's not "not a problem at all" either.

  • HoyaSaxa 9 years ago

    Just to illustrate a potential scenario:

    You use a piece of open source software that has a javascript file which the author has hosted on a CDN. In order to mitigate an attack where someone changes the content of the javascript file you use a hash[1] to ensure the file does not change. You think you are safe, but a malicious actor gets access to the CDN and instead of changing the file, instead builds a quick scripts that looks for password reset referrer patterns and races the human to reset the password first. A computer is probably going to win that game that majority of the time.

    [1] https://github.com/twitter/secureheaders#hash

    • CGamesPlay 9 years ago

      Or the attack just skips the secure hash check on the server... This example doesn't seem to hold water either.

mmanulis 9 years ago

Why not pass the token as an HTTP header? e.g. X-RESET-TOKEN=foo and then read that via JS

  • dom0 9 years ago

    How exactly do you encode a HTTP header into an URL?

  • derekprior 9 years ago

    You can't set an HTTP Header with an HTML link.

    Would be nice to set a body though, wouldn't it? Also, not allowed by HTML.

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection