Problem / Motivation

Screen recording: https://drive.google.com/file/d/1Psq-aeG_j7XH3BTj_jl2eMz8ekb32vqy/view?u.... Browser: Firefox.

Originally reported as a security issue by David Rothstein, but making it into a public issue as the security team considers this non-critical and thus can be fixed in the public queue.

=====
Testing with Firefox, if you log out of a Drupal site and then hit the back button, you can see pages from the authenticated user's previous session.

This could be a problem on public computers, if the authenticated user had permission to see content protected by node access (or similar).

Variations of this have been reported in the public issue queue in many places. For example:

We might say it's OK to discuss in public (and it certainly would be hard to find and unpublish all the existing issues now anyway) but I thought it might be useful to discuss here first.

As far as I can tell, the simplest way to fix this is to add the "no-store" header to all pages viewed by an authenticated user; however, Drupal used to do this but it was removed a long time ago (see http://drupal.org/node/109941) because it was causing all sorts of annoyances when an authenticated user tried to use the back button while still logged in. So, it's possible there isn't really a great solution here.

Security Note

Back and refresh attack

Screen recording: https://drive.google.com/file/d/1Psq-aeG_j7XH3BTj_jl2eMz8ekb32vqy/view?u.... Browser: Firefox.

There is a potential security concern here: imagine a user on a public computer. They log in to Drupal, go to pages that only an authenticated user should see, then logout. Unless the browser session was closed (the in-browser caches cleared), then someone else could come to that computer and click the back button to see those pages.

While the risk applies to anything you can see on the screen, it can also apply to anything in markup, notably a password field. See https://resources.infosecinstitute.com/browser-based-vulnerabilities-in-... for a generic description of this approach. The example would be someone attempts to login, it fails, and their password is still in the markup. In my testing, I did not see this issue with Drupal 8 because on a failed password attempt the password is cleared out (i.e. its not sent back to the browser). But, the principle of this approach could apply to other fields. If webforms module is used to submit important information, this issue could apply.

On Firefox, you can close the tab or close the browser (quit the browser application), then open the application again and be able to go back in history. In this scenario, the browser's setting is to "Open up previous tabs".

The user can visit multiple previous pages, not just the most recent.

Browser disk cache

Separately, but related, there is a concern about the browser storing these caches on disk. If those caches are not cleaned up, then anyone with access to that disk can access those cached pages.

Examples of this Attack

Steps to reproduce

1. Login to Drupal
2. Go to a page
3. Logout
4. Note you are redirected to the home page
5. Click the browser's "Back" button
6. Note you are now at the page from Step 2 and you see the Drupal admin toolbar (which should only show if you're authenticated)

This issue can be observed in Chrome and Firefox. With Chrome, if you disable cache using browser tools the issue is not observed. With Firefox, if you disable cache using browser tools the issue is still observed.

The issue persists even if you close the tab or quit the browser (tested in Firefox).

The user can click "Back" multiple times to see even older cached pages.

Proposed Resolution

Allow the user to use the back button, but change its behavior based on authenticated status. There are two basic scenarios:

Scenario 1: Logged out to logged in

1. Go to a page
2. Login to Drupal
3. Click the browser's "Back" button
4. Go to the page from Step 1, but you now see the "logged in" version of the page

Scenario 2: Logged in to logged out

1. Login to Drupal
2. Go to a page
3. Logout
4. Click the browser's "Back" button
5. Go to the page from Step 2, but you now see the "logged out" version of the page

The details on how to achieve this are TBD.

Potential Approaches

Here are some approaches discussed, it's uncertain which of these work. Please update this ticket to verify which do.

Option 1: JavaScript History API

See https://www.drupal.org/project/logout_redirect

Option 2: JavaScript window.onpageshow

See https://gomakethings.com/fixing-safaris-back-button-browser-cache-issue-...

Option 3: Cache-Control no-store

See #3130912: Incorrect Cache-Control headers for authenticated users

Option 4: Clear-Site-Data: "cache"

See https://www.fastly.com/blog/clearing-cache-browser
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data

Reference Material

Issue fork drupal-1912514

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

dokumori’s picture

Issue summary: View changes

updated the description slightly

greggles’s picture

Adding a related issue to fix this in paranoia.module.

matt2000’s picture

Another possible way to mitigate this, at least in my testing with Chrome, may be to use the Vary:Cookie header, even for authenticated users. The private data may still be stored somewhere by Chrome, but at least a simple Back button click doesn't reveal it.

pwolanin’s picture

Don't we already send vary: cookie? It looks like D7 always does unless you suppress it (e.g. for a CDN).

Drupal 8 seems to have similar logic.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Hardik_Patel_12’s picture

For Drupal 8 you can follow this link "https://www.drupal.org/project/logout_redirect" to solve this issue.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

klausi’s picture

I experimented a bit with the "no-store" cache control header. If that is set as default header for logged-in users then form field entries are lost when you click away by accident and use the back button. I think that would be a UI regression? We deliberately introduced the store option (now a default in browsers I assume) in #109941: Store site view in HTTP headers many years ago so that users do not lose context when they navigate back.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

pwolanin’s picture

Wonder if this could be solved by having the user land on a page after logout that loads with no-store, then redirects?

Or maybe we could also/instead use JS history API to replace the entries for the current window?
https://developer.mozilla.org/en-US/docs/Web/API/History_API

Not sure either of these are bullet-proof in terms of eliminating authenticated content being stored by the browser.

However, there might be an even better option, a response header to ask the browser to clear cache:
https://www.fastly.com/blog/clearing-cache-browser
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data

Seems to have reasonably broad browser support now.

alexpott’s picture

FileSize
584 bytes

Adding a Clear-Site-Data: "cache" cache on logout would do the trick. But won't people be upset that now their browser has to download all the assets which are common between the logged in sites and logged out site?

Anyhow I had a quick test of the header and it worked great on Chrome but not in Firefox?!?! :( I tried both with and without quotes and with cache and *. Without quotes worked on chrome - it never worked on firefox. :(

nandeesh95’s picture

nandeesh95’s picture

Assigned: Unassigned » nandeesh95
josephdpurcell’s picture

Issue summary: View changes

This issue was reported on a site build I'm working on. After lots of searching and trying to find the right search terms, I found this issue! It seems to describe what I'm seeing.

I'll add some reference material to the description of the ticket.

The comment from #14 is insightful. I see #3130912: Incorrect Cache-Control headers for authenticated users is planned to implement the "no-store" flag in the Cache-Control headers, I don't know if that will affect this ticket. I also see https://www.drupal.org/project/logout_redirect.

I'm going to document these various approaches in the description.

josephdpurcell’s picture

Issue summary: View changes

I'm going to have a go at describing the proposed resolution so that it's clear what this ticket is trying to achieve. Please revise as anyone sees fit.

josephdpurcell’s picture

Issue summary: View changes
josephdpurcell’s picture

Issue summary: View changes

Adding a screen recording of the issue.

josephdpurcell’s picture

Issue summary: View changes
josephdpurcell’s picture

Issue summary: View changes

Sorry for all the updates, but I am going down the rabbit hole and finding important information.

josephdpurcell’s picture

Issue summary: View changes

It seems there are two objectives, one functional and one security related.

Functional objective: ensure a user who is browsing along and hits the back button doesn't see an incorrect logged-in state. That is simply confusing.

Security objective: ensure that Drupal sites with sensitive information are not vulnerable to the two attacks described.

It seems like with some research and testing, an approach could be defined to address those objectives. However, it gives me concern as I don't know what indirect impacts are involved with various approaches.

Maybe we could focus on the security issue first? It seems like Drupal should have a default of Cache-Control: no-store for authenticated traffic, which is covered by #3130912. I will need to do some testing, but in the research I've done that seems to be what others are pointing to as the solution. I'm wondering if a ticket should be opened with webforms to have some flag on every webform that declares whether its a sensitive form, and if it is send the no-store header.

josephdpurcell’s picture

I did a test of patch #5 on #3130912 and found it to meet the security objective of not exposing authenticated information through the back button attack. But, it did not meet the functional objective of an authenticated user clicking the "Back" button and seeing an anonymous page. See details here: https://www.drupal.org/project/drupal/issues/3130912#comment-13767062

drumm’s picture

Issue tags: +affects drupal.org

We’ve had a bit of an uptick in security researchers reporting this issue about Drupal.org.

pwolanin’s picture

Issue summary: View changes

Looks like Clear-Site-Data header now is supported by pretty much all browsers other than safari

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

DieterHolvoet made their first commit to this issue’s fork.

DieterHolvoet’s picture

Looks like Clear-Site-Data header now is supported by pretty much all browsers other than safari

I wouldn't say that, Clear-Site-Data is not supported on Firefox and Safari. It was supported on Firefox at some point, but later moved to a feature flag because the implementation turned out to be incomplete/problematic. There doesn't seem to be a lot going on in the issue, so I'm afraid we'll have to wait a little longer before being able to use this feature.

I tested the patch on Google Chrome, the header is successfully added to the logout response but it looks like I'm still able to access cached admin pages by using the back button. Am I doing something wrong?

DieterHolvoet’s picture

Status: Active » Needs work
Rakhi Soni’s picture

Assigned: nandeesh95 » Unassigned
Status: Needs work » Needs review
FileSize
579 bytes

Attached rerolled patch against 9.4x,,
Kindly review patch,,

DieterHolvoet’s picture

Status: Needs review » Needs work

A MR was opened, so no need for a rebase. More work is still needed so setting back to Needs work.

bnjmnm’s picture

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

znerol’s picture

OWASP has a page in the WSTG about this topic. Their recommendation:

The Back button can be stopped from showing sensitive data. This can be done by:

  • Delivering the page over HTTPS.
  • Setting Cache-Control: must-revalidate

This is basically what Drupal is doing already. Except that there are some additional directives on authenticated pages (Cache-Control: must-revalidate, no-cache, private). I wouldn't expect that the extra directives no-cache and private would reverse the effect of must-revalidate. So I looked at other response headers relevant to browser caching.

Turns out that we are missing Vary: Cookie on authenticated responses. The culprit is FinishResponseSubscriber::setResponseNotCacheable(). The comment in this method says:

    // There is no point in sending along headers necessary for cache
    // revalidation, if caching by proxies and browsers is denied in the first
    // place. Therefore remove ETag, Last-Modified and Vary in that case.

There is only one situation where this behavior would be correct: On a selected route which is delivered with Cache-Control: no-store in all cases (independently whether a session is open or not).

In all other situations, the Vary: Cookie header must be on every response. No matter whether a user is logged in or not or a session is open or not.

znerol’s picture

Version: 9.5.x-dev » 10.1.x-dev
Component: other » base system
Assigned: Unassigned » znerol

znerol’s picture

Assigned: znerol » Unassigned
Status: Needs work » Needs review

MR !3505 adds Vary: Cookie to every response. Obviously needs manual browser testing.

znerol’s picture

Status: Needs review » Needs work

MR !3505 doesn't seem to fix the problem. Interestingly this wasn't an issue on the previous build of the site I am mainly working on. I get a 403 (i.e., the expected behavior) with the following testing scenario in Firefox:

  1. GET /
  2. Log in -> 302 to /
  3. Navigate to profile form (/user//edit)
  4. Logout -> 302 to /
  5. Hit browser back button -> 403

The legacy site was based on Drupal 7 and Authcache. Authcache encourages the browser to cache pages even for authenticated users. However, it also ensures that the ETag is different for each variant of a page.

I think that when Authcache is active, the browser is replacing the cached frontpage in step 2 (after login), and the cache is replaced again in step 4 (after logout). My hunch is that a cache replacement (in contrast to a cache insert) invalidates previous history and as a result the browser attempts to refetch the user page when the back button is hit.

Pure speculation though, more testing is clearly necessary. Also I am a bit disappointed by the OWASP page linked in #39. It doesn't seem to cover the full problem.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Pemson18’s picture

sokru’s picture

Should we consider Option 3:Cache-Control: no-store? On linked issue #3130912: Incorrect Cache-Control headers for authenticated users there's patches to implement it with test coverage. OWASP recommends the approach also on its FAQ page https://owasp.org/www-community/OWASP_Application_Security_FAQ#how-do-i-...

Twitter.com and Google seems to rely on Cache-Control: no-store header on authenticated services.

Fastly article from 2014 states about Vary: Cookie: "Cookie is probably one of the most unique request headers, and is therefore very bad."

sokru’s picture

I tested @znerol's MR !3505 with DDEV, it works only with HTTPS enabled, like mentioned on OWASP documentation. Tested with Firefox, Chrome, Edge. Unfortunately on Safari the back button showed the cached content.

Wrote on #3130912: Incorrect Cache-Control headers for authenticated users that we should implement the Cache Control header change on that issue. It will partially fix this issue. On Safari there's a bug https://discussions.apple.com/thread/251817133 and I hope we could narrow down this issue just for Safari.

sokru changed the visibility of the branch 1912514-fix-vary-2 to hidden.