---
title: Is Your Site Leaking Password Reset Links?
teaser: Emailed password reset links are a common part of web applications. Is your
  site leaking these confidential links to third party sites?
tags: web,security
author: Derek Prior
published_on: 2016-10-24
---

Self-service password resets are a common part of many web applications. The
typical password reset link is emailed to the user and contains a unique token
that in some manner identifies the user. By clicking the link, the user proves
they have access to the email associated to the account, and has now
authenticated using a second factor. At this point, they are asked to provide a
new password.

If an attacker were able to access the password reset link, the attacker would
then be able to authenticate as the user and provide that new password, thus
gaining unfettered access to the user's account. Attackers frequently accomplish
this by gaining access to the user's email, though as I recently discovered
**many applications are unwittingly passing password reset links to third party
sites**. To understand how, we must first understand how websites gather
referrer data.

## The HTTP `Referer` Header

The HTTP `Referer` header (the misspelling of "referrer" is an unfortunate
mistake of history that I begrudgingly replicate here when referring to the
header) is sent to the server by your browser and identifies the URL of the
requesting site. It's chiefly used by site operators for the purposes of
analytics. Referrer data is how site operators know which search terms people
are using to find their site, or how many visits their latest social media blitz
drove. The browser sends this header when users click links or when the browser
fetches a resource such as an image, a stylesheet, a JavaScript file, or a video
that is referenced in the document being rendered.

Browsers will not send the `Referer` for resources fetched via HTTP from a
document loaded via HTTPS. Additionally, some browsers respect aspects of the
[referrer policy spec], which allows authors to control the conditions under
which the full referrer will be revealed in the `Referer`.
Unfortunately, support for this spec is not yet universal and it should not be
solely relied on for security concerns.

[referrer policy spec]: https://www.w3.org/TR/referrer-policy/#referrer-policy-delivery-meta

## Leaking Password Reset Links

When the user requests a password reset, they are emailed a link that looks
something like this: `https://example.com/passwords/edit?token=1234abcd`. When
the user clicks that link, the application renders the password reset form
inside the usual site layout, which may contain references to assets loaded from
a trusted content delivery network (CDN) or an analytics package such as
Segment. The layout may also contain links to external sites such as the
company's social media profiles.

Barring an intermediate redirect between the user clicking the link in their
email and the password reset form being rendered, the browser will expose the
password reset link in the `Referer` sent when requesting the
referenced assets or the analytics package. If the user does not complete the
password reset and instead clicks on one of the external links, the password
reset link will be leaked via the `Referer` on those requests as well.

The seriousness of this leak is mitigated by several factors:

-   Most password reset tokens are invalidated once the user completes the
    password reset form and thus the window for using a leaked password reset
    link is likely to be pretty small.
-   Barring user generated content being improperly rendered on the password
    reset page, an attacker cannot control which sites the token is leaked to.
-   If the token is leaked, it is most likely to be leaked to a site that you
    consider to be a trusted partner.

These factors make the leak something that is unlikely to be easily exploited.
While it's hard to imagine a nefarious employee at your trusted CDN waiting to
act immediately on incoming referrer data, it's less hard to imagine an attacker
targeting that same CDN and stumbling on this potentially valuable information
in server logs. For that reason, it's important to address this issue even if
the likelihood of an immediate exploit is low.

## Testing Your Site

You can perform the following test on your own site to see if it is possible for
it to leak working password reset links to third party sites.

1.  Request a password reset and click the link that is emailed to you.
2.  Copy the URL from the resulting page.
3.  Open a private browser window or a different web browser and paste the
    copied URL.

If a working password reset form is rendered then you have proven that without
any mitigating steps, any `Referer` generated by this page would
contain a working password reset URL.

You can use the networking tab in your browser's web inspector to inspect all of
the requests generated by the page to see if any of them leaked the password
reset link externally in the process of rendering the page. You can inspect all
HTML links in the document to see if any of them point to an external HTTPS
resource and thus would leak the password reset link if clicked.

<figure>
  <img
    src="https://images.thoughtbot.com/blog-vellum-image-uploads/hrAjpvGWSXa4zpsEgI2D_password-reset-link-leak.png"
    alt="A leaked password reset link displayed in the Chrome developer tools"
  />
  <figcaption style="text-align:center;">
    Here we see Upcase leaking password reset links to its CDN, Cloudfront.
  </figcaption>
</figure>

If the page loaded in your private browser instance, but it does not fetch or
link to any external resources then it is not currently leaking the password
reset link. However, you should still take steps to eliminate this possibility
altogether as any future changes to, for instance, footer links or external
resources loaded could cause this to be a problem.

## Plugging The Leak

This leak can be plugged by ensuring that the URL of the page that is rendered
as a result of clicking the password reset link does not contain a valid
password reset token. When this issue was reported and fixed in Clearance, our
authentication engine for Rails, we considered the following two fixes:

1.  Update the `passwords#edit` controller action so it immediately invalidates
    the password reset token in the request and generates a new one that is used
    in the form action. The `Referer` will still contain the original
    password reset token, but the token is no longer valid.
2.  Update the `passwords#edit` controller action so it detects the presence of
    a token in the URL, stores that token in the session, and redirects back to
    `passwords#edit` with the token removed the URL. The `Referer`
    will no longer contain the necessary token.

We went with the session-based approach in Clearance 1.15.0 because we felt the
immediately expiring password reset link in the first approach broke idempotent
`get` requests. You can see how each of these approaches changed our code and
the discussion of them both in the pull requests I submitted for [the
session-based approach] and [the immediate expiration approach]. If you use
Clearance, you can update to Clearance 1.15 or newer to fix this issue.

[the session-based approach]:https://github.com/thoughtbot/clearance/pull/707
[the immediate expiration approach]:https://github.com/thoughtbot/clearance/pull/706

If you'd like to hear more about the evolution of this issue and it's fix in
Clearance, you can listen to episodes [81] and [82] of The Bike Shed, a podcast
about web development.

[81]: http://bikeshed.fm/81
[82]: http://bikeshed.fm/82

## Thank You

Thank you to [Aditya Prakash] for bringing the issue with Clearance to my
attention and to [Jeroen Visser] for assisting in defining its scope.

[Aditya Prakash]: https://twitter.com/sonalkr132
[Jeroen Visser]: https://twitter.com/jeroenvisser101
