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
| 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...
| Comment | File | Size | Author |
|---|---|---|---|
| #13 | 2508637-12.patch | 1.38 KB | pwolanin |
Comments
Comment #1
pwolanin commentedComment #2
pwolanin commentedComment #3
tim.plunkettIt's nice to have one tag to find all of the public critical security issues, and this is on 5 of them already.
Comment #4
xjm@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.
Comment #5
catchThis reminded me of https://www.drupal.org/node/731710 might be worth checking that issue too.
Comment #6
joshi.rohit100I am not sure if its right - SessionManagerInterface has delete() and destroy() method. Are we using this interface ?
Comment #7
catchYes 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.
Comment #8
joshi.rohit100This - https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Session!SessionMa... ?
Comment #9
fabianx commentedYes, seems we don't need an API change for that - as the Interface supports it :). Nice.
Comment #10
pwolanin commentedWe 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.
Comment #11
pwolanin commentedPlease 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.
Comment #12
catch@pwolanin that's exactly what we said? Regenerating the current session after deleting all should preserve the current session?
Comment #13
pwolanin commentedHere'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.
Comment #14
pwolanin commented@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?
Comment #15
fabianx commentedI 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.
Comment #16
mparker17As a sprinter at DrupalNorth, I will help by reviewing this!
Comment #17
mparker17Updated issue summary.
Comment #18
pwolanin commentedok, 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?
Comment #19
mparker17Unassigning as I didn't make much progress on the code today.
Comment #20
pwolanin commentedComment #21
webankit commentedIgnore this
Comment #22
mparker17@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.
Comment #23
vijaycs85This is already in core (including the case mentioned by @pwolanin in #18). Am I missing something here?
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.
Comment #24
berdirI 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.
Comment #25
pwolanin commented@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.
Comment #26
catchWhy would that be buggy? The session information is in $_SESSION.
Looks like it explicitly protects against that by checking the account ID vs. current user ID.
Comment #27
mparker17Comment #28
pwolanin commented@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)
Comment #29
znerol commented#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.
Comment #30
znerol commentedI quickly tested that manually on D8 and D7 and this seems to work like expected. Did the bugcrowd submitter actually provide a test scenario?
Comment #31
pwolanin commentedI'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.
Comment #32
znerol commenteddrupal_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?
Comment #33
pwolanin commentedPossibly they tested against drupal.org or another site not using DB sessiom
Comment #34
znerol commentedShould we raise the priority of #2472535: Remove SessionManager::delete in favor of a portable mechanism to invalid sessions of authenticated users then?
Comment #35
pwolanin commented@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.
Comment #37
sksanjoo2 commentedGetting Issue on Drupal 10
Comment #38
sksanjoo2 commented