Follow-up to #2508627: Changing email address should invalidate one-time login links

Problem/Motivation

Assume I realize I left myself logged into a shared computer to my Drupal site account.

I change my password to protect myself.

However, the session on the shared computer is NOT invalidated and anyone with access to that machine continues to have access to my Drupal site account.

Proposed resolution

Add to the session API a method that allows invalidating all sessions based on username or uid.

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Arguably a bug because the sessions should be invalidated and the D7 workaround is not possible (?) in Drupal 8. Maybe a task.
Issue priority Major because it is a valuable security hardening that leads to bug reports for many Drupal hosts. Not critical because the issue also exists in Drupal 7 core and is not considered a critical security issue there.
Prioritized changes The main goal of this issue is security.
Disruption Some disruption for existing session backends because they must implement additional API.

Remaining tasks

  • Add a method to \Drupal\Core\Session\SessionManager\SessionManager, similar to \Drupal\Core\Session\SessionManager\SessionManager::delete(), that deletes all sessions EXCEPT the current one — suggested ::deleteOther() — see patch in #13.
  • Write tests
  • Review and RTBC.

User interface changes

n/a

API changes

API addition

Need to expand the session API to support invalidating all sessions for a user, and make that an API back-ends need to support in some way.

Because of that requirement for back-ends to support it, this is not something that can readily be added in 8.1.x so must be added to 8.0.x

Data model changes

None

For drupal 7 there are contrib solutions that work only for SQL session storage: https://www.drupal.org/node/2294061

original report was part of the Drupal 8 bug bounty
https://tracker.bugcrowd.com/submissions/39a728dfa89b4029bbc15499c410b97...
https://tracker.bugcrowd.com/submissions/756a3dbf67e9f3e917f4525c0ef9c35...

CommentFileSizeAuthor
#21 2508637-21.patch1.83 KBwebankit
#13 2508637-12.patch1.38 KBpwolanin

Comments

pwolanin’s picture

Issue summary: View changes
tim.plunkett’s picture

Issue tags: +Security

It's nice to have one tag to find all of the public critical security issues, and this is on 5 of them already.

xjm’s picture

Priority: Critical » Major
Issue summary: View changes

@pwolanin, @effulgentsia, and I discussed this issue this morning. I think that this issue is more of a major security hardening than a critical bug, given that it's an known and accepted issue in D7 core.

@pwolanin mentioned that this would require an API addition and a possible BC break for session backends, and I think that controlled BC break is definitely acceptable during the beta for the security improvement.

catch’s picture

This reminded me of https://www.drupal.org/node/731710 might be worth checking that issue too.

joshi.rohit100’s picture

I am not sure if its right - SessionManagerInterface has delete() and destroy() method. Are we using this interface ?

catch’s picture

Yes I'm also confused, https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Session%2... has this already so is it just a case of calling that on password changes? Need to make sure the user doesn't get logged out due to that though from their current session.

joshi.rohit100’s picture

fabianx’s picture

Yes, seems we don't need an API change for that - as the Interface supports it :). Nice.

pwolanin’s picture

We need a new method that destroys ALL sessions for the user, except the session they are currently using.

In other words, if I'm logged in at work and at home, and I change my password at home, the work session needs to be destroyed.

pwolanin’s picture

Priority: Major » Critical

Please look at the patch in the paranoia module issue at at the top - so we need a method like delete($uid), but that excludes the current session.

catch’s picture

@pwolanin that's exactly what we said? Regenerating the current session after deleting all should preserve the current session?

pwolanin’s picture

Status: Active » Needs review
StatusFileSize
new1.38 KB

Here's a quick patch to show the kind of interface change we might need.

Still needs to be wired up to the password change.

Also makes me wonder if we should be using uuid instead of uid to track sessions - would be nicer for external integrations perhaps.

pwolanin’s picture

@catch. Hmm, I see - so that might work to keep the used logged in, but it looks like you might lose any session data if you did that?

It also seems like that could depend a lot on implementation details as opposed to having a method that specifically deletes those other than the current session?

fabianx’s picture

Priority: Critical » Major
Issue tags: -Security

I think this is only an API addition, which can go in as major bug as well.

A BC break through adding of a method to an interface should still be fine in limited circumstances. After all you could just do:

throw new \InvalidArgumentException('Method not implemented.');

to fix your custom session interface in a few minutes, then do it properly later.

mparker17’s picture

Assigned: Unassigned » mparker17

As a sprinter at DrupalNorth, I will help by reviewing this!

mparker17’s picture

Issue summary: View changes

Updated issue summary.

pwolanin’s picture

Status: Needs review » Needs work

ok, it actually needs work to add the password change integration.

In addition, would it make sense to invalidate all of a user's sessions if an admin changes their password?

mparker17’s picture

Assigned: mparker17 » Unassigned

Unassigning as I didn't make much progress on the code today.

pwolanin’s picture

webankit’s picture

StatusFileSize
new1.83 KB

Ignore this

mparker17’s picture

@webankit, I think you may have posted the patch to the wrong issue: the patch in #21 tests that URLs can be parsed properly; this issue is about clearing all but the current session when a user changes their password.

vijaycs85’s picture

This is already in core (including the case mentioned by @pwolanin in #18). Am I missing something here?

core/modules/user/src/Entity/User.php:109
    if ($update) {
      $session_manager = \Drupal::service('session_manager');
      // If the password has been changed, delete all open sessions for the
      // user and recreate the current one.
      if ($this->pass->value != $this->original->pass->value) {
        $session_manager->delete($this->id());
        if ($this->id() == \Drupal::currentUser()->id()) {
          \Drupal::service('session')->migrate();
        }
      }

However I'd take this opportunity to dispatch an event (say UserPasswordEvent::UPDATE), so that contribute modules can add any additional behaviour if necessary e.g. expire session in external authentication system like OpenAM.

berdir’s picture

I was discussing this a while back with znerol. We should make sure to get him in here.

The problem I believe is that alternative backends like Memcache have no easy way of doing a query like the patch above. It would require some trickery for them, like maintaining a list of session ID keys per UID or so.

He was suggesting that we instead add a mechanism that allows us to verify if a session is still valid when loading IIRC, but I can't remember the details.

pwolanin’s picture

@Berdir - yes, for memcache possibly you have to maintain that mapping or come up with some other clever scheme, which is why I think this change needs to be made now.

But they will already have to do so to support the delete() method, so there is no real change here to the requirements.

Redis can use operations like SCAN or KEYS if you prefix the key with the uid.

@vijaycs85 - so the current code looks like it might be buggy since it deletes the current session's data from the table and then regenerates it? In any case, it's not clear what's going to happen if an admin is changing your password - looks like the admin's session is regenerated? That would also seem to be a bug.

catch’s picture

so the current code looks like it might be buggy since it deletes the current session's data from the table and then regenerates it?

Why would that be buggy? The session information is in $_SESSION.

In any case, it's not clear what's going to happen if an admin is changing your password - looks like the admin's session is regenerated?

Looks like it explicitly protects against that by checking the account ID vs. current user ID.

mparker17’s picture

Issue tags: +DrupalNorth2015
pwolanin’s picture

@catch - maybe it's correct to delete and then migrate if the information in $_SESSION is always more current than what's in the database.

Yes, I misread the code as far as current user (the ->id() here really being uid not session ID)

znerol’s picture

#23 is right, this is already implemented. @pwolanin did you test this?

Also, there is already a proposal on how this can be implemented in a storage agnostic way: #2472535: Remove SessionManager::delete in favor of a portable mechanism to invalid sessions of authenticated users.

znerol’s picture

I quickly tested that manually on D8 and D7 and this seems to work like expected. Did the bugcrowd submitter actually provide a test scenario?

pwolanin’s picture

Status: Needs work » Fixed

I'm quite sure the does *not* work on Drupal 7, which is where it was actually reported. The core API doesn't support it, and you have to add on with Paranoia and only for SQL back ends.

For 8 I think we can call this fixed for core, but we need to make contrib adhere to the contract also.

Unless we want n API addition to 7 to add it we can call this fixed for core.

znerol’s picture

drupal_session_destroy_uid()

I did use two browser windows (one in private mode) to perform #30, so I'm pretty sure it does work both in D7 and D8. That's why I wonder whether the original submitter did provide a test scenario, perhaps there is some way to circumvent that mechanism?

pwolanin’s picture

Possibly they tested against drupal.org or another site not using DB sessiom

pwolanin’s picture

@znerol - if other back-ends cannot support delete(), then yes we should make sure this is in some other way an enforced part of the session API.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

sksanjoo2’s picture

Getting Issue on Drupal 10

sksanjoo2’s picture

Version: 8.0.x-dev » 10.0.x-dev
Assigned: Unassigned » sksanjoo2