Closed (fixed)
Project:
Drupal core
Version:
11.x-dev
Component:
base system
Priority:
Normal
Category:
Bug report
Assigned:
Unassigned
Issue tags:
Reporter:
Created:
8 Dec 2015 at 18:31 UTC
Updated:
3 Jun 2025 at 20:08 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #2
kscheirerComment #3
kscheirerComment #5
kscheirerThe number of fails looks unrelated? Testing again.
Comment #7
cilefen commentedLogins are failing in the tests.
Comment #8
kscheirerComment #9
kscheirerThis patch has better naming. Thank you @cilefen for diagnosing the problem.
Comment #10
tstoecklerSo this returns NULL if
$sidis empty. We should update the docs or (preferably, IMO) fix the code to also returnFALSEin that case.Minor, but to me it's a bit weird to fetch the timestamp in SQL and then cast it to bool in PHP. What about a
timestamp <> 0condition?Comment #11
znerol commentedThe SessionManager should have no db-dependency at all. Regrettably we failed to remove all of them before release. So if possible move that to somewhere else.
Also we should check whether Symfony is affected and if so, fix the bug there.
Comment #12
znerol commentedI did a bit more research on the subject of unauthenticated session fixation attacks. The OWASP Session Management Cheat Sheet points out that...
Regrettably, the cheat-sheet does not mention unauthenticated cases. However there are quite some scenarios where unauthenticated session fixation might pose a problem. Citing from the linked blog post:
Comment #13
gregglesRe #12, I agree it's not 100% clear what the attack scenario is or what the vulnerability is, that's why this is in public rather than being handled in the private tracker (I created an issue in the private tracker and the team consensus was that it could be public).
One good reason for this patch is just that sites without it will end up with invalid sids in their sessions storage. The number of invalid sids depends on the likelihood of the site to get requests with invalid sids, but...let's say it can pretty easily number into the millions for a site with a long-lived session cookie.
Another good reason is just data validity and avoiding collisions. If a user passes a Drupal site a sid that is not in the sid storage, there's no way to guarantee it's a good value. Drupal works hard to create a random sid and we shouldn't let a user create a bad one for themselves.
Comment #14
znerol commentedOh, the attack scenario is rather detailed in the second link I've posted. But the provided patch does not mitigate it. Given a multistep form with summary screen at the end. The attacker generates a session (e.g. by partially filling out the form) and fixates the sid in the users browser. While the victim is filling out the form, the attacker collects private data of the victim.
Regarding user chosen sids: We actively remove empty sessions for unauthenticated users. Therefore if you just manually add a generated session cookie to the browser, then the cookie is supposed to be removed at the first request to the site unless the site sets session data.
Comment #15
gregglesI would say that 1 attack scenario is detailed and that this code doesn't address that scenario. IMO Drupal cannot know which events are worthwhile to change the session identifier to avoid that kind of attack and the responsibility is on the site builder to do that.
In my experience that does not happen. I think this code fixes this bug.
Comment #18
marvil07 commentedPatch still applies correctly, and based on comment #15 marking as RTBC.
Comment #19
alexpottThis looks like a testable bug - to ensure we don't break it in the future.
Comment #21
gregglesHere is a Drupal 7 patch.
Comment #29
neclimdul#2238561: Use the default PHP session ID instead of generating a custom one broke this. Started to re-roll but that moved all the session ID hashes and almost all the database logic out of the manager and adding it back smelled a bit. I wonder how PHP/symfony handle this. Is there some subtle changes we can make to the handler to just trigger their handling of this? For example, the ::read documentation is "If nothing was read, it must return false." so we aren't following the interface and maybe that where they'd normally regenerate a bad session?
Comment #30
neclimdulactually, trying to recreate this on a clean 9.4.x site the cookie is getting deleted. So... maybe fixed? Someone else want to confirm?
Comment #31
neclimdul#14/#15 suddenly make more sense. I was seeing the cleanup of the empty sessions not actually cleaning up invalid sessions. The summary talks about "gives sessions to anonymous users" but its actually the process of adding something _to_ the session that triggers the behavior.
Comment #32
neclimdulSo the previous approach works but it also looks like one of the reasons this is happening because we aren't really taking full advantage of the php session management because we aren't implementing the poorly named SessionUpdateTimestampHandlerInterface. SessionUpdateTimestampHandlerInterface::validateId works like I thought returning false from read might have worked if session.use_strict_mode is enable.
Also interesting, Symfony has code for this we just aren't using it for some reason. I took a pass at adopting that code and with some manual testing it seems to work. 2 Changes where needed
One thing to note, there is a manual cookie mangling in the ::destroy method of Symfony's base class that I bypassed. I'm not sure why they're handling it there but we didn't do that and it seems like we must be handling that in some other way. Probably deserves some review though.
There are some BC issues with this approach.
Comment #34
andypostComment #35
andypostAccording to #1561866-98: Add support for built-in PHP session upload progress upload progress may need separate cookie
Comment #36
andypostand new MR for 11.x
Comment #37
kentr commentedComment #39
kentr commentedThere's an MR for review. I commented on the MR in Gitlab.
Comment #40
znerol commentedThanks @kentr for picking this up (and thanks @neclimdul for the initial work). In the mean time #3377256: Correctly implement SessionHandlerInterface and #3386766: Add return types to SessionHandler landed, so we do not have any typing related BC problems here anymore.
I'll stick around for reviews, let's land this.
Comment #41
bbralaReview was done, this added feedback. Setting to needs work.
Also; reroll no longer needed and there are tests. Removing tags.
Comment #42
bbralaComment #43
kentr commentedComment #44
kentr commentedComment #45
kentr commentedSuggestions applied. There are a few outstanding threads.
Comment #46
znerol commentedTest result of the failing test:
This is in testSessionWrite().
My hunch is that now that
SessionHandlerimplementsupdateTimestamp(), thetestSessionWrite()fails because session data is now updated where it wasn't before.Many repeated writes into the session table when no update happened could lead to spiking db traffic on existing sites. So I suggest to just add a stub in this issue and asses whether / how we want to implement
updateTimestamp()in a follow-up. Maybe we need logic to throttle updates to thetimestampcolumn.Comment #47
znerol commentedI've been researching this a little bit. We have the following mechanism in core already:
180seconds.SessionHandler:write()is called when the request terminates.timestampfield.Hence, in order to avoid unnecessary writes, the
updateTimestamp()method must not touch the database. Instead it should justreturn FALSE;.Comment #48
kentr commentedReturning
FALSEfromupdateTimestamp()causes PHP warnings and errored tests. I believe the error originates from PHP session.c, line 521.I pushed it so that others can observe, but I believe
updateTimestamp()should returnTRUEto be seen as a successful no-op.In Watchdog:
In tests:
Comment #49
znerol commentedYes, you are right. A no-op implementation of
updateTimestamp()needs to returnTRUE.Comment #50
kentr commentedOk. I changed
updateTimestamp()to returnTRUE.Comment #51
znerol commentedThere is one test failure:
I've seen that failing randomly in other MRs. Triggered a rerun in order to check whether this is reproducible.
Comment #52
znerol commentedTests passed, including the one which failed before:
Comment #53
znerol commentedFantastic work so far. Thanks a lot @kentr. While thoroughly reviewing existing tests I found one thing which indicates lack of coverage:
The MR adds new methods to
TestSessionHandlerProxy.php. That class is used to verify correct propagation of session handler calls throughout the handler stack.However, those new methods seemingly do not have any effect on the test which is asserting on the results of
TestSessionHandlerProxy, namely StackSessionHandlerIntegrationTest.Looking at the test, I think it only covers a situation where a new session is opened. But it doesn't cover the situation where the new methods are called from within the php session subsystem. I.e., it doesn't cover the case when an existing session is used (valid session cookie exists on the request). E.g., on a subsequent read (without any write to the session), I'd expect a trace including
validateIdbeforereadandupdateTimestampinstead ofwrite.In order to produce such a trace, it would be necessary to extend the
SessionTestController.phpwith a method similar to traceHandler(), maybe asserting that$_SESSION['trace-handler']is non-zero but without changing it. TheStackSessionHandlerIntegrationTest::testRequestthen could subsequently call into that and assert that the new methods onTestSessionHandlerProxyare actually invoked.Comment #54
znerol commentedAdded a CR draft.
Comment #55
kentr commentedGot it. I thought it was OK because the phpDoc documentation gives examples of providing additional details along with
{@inheritdoc}.I'll look into these tests.
Comment #56
kentr commentedMy understanding is that
updateTimestamp()is only invoked when session data is saved and the data is unmodified. This matches my observation with the traces.So, I added trace tests for these cases:
validateId()is invoked.write()is not invoked.updateTimestamp()is not invoked.validateId()is invoked.write()is invoked.updateTimestamp()is not invoked.validateId()is invoked.updateTimestamp()is invoked.write()is not invoked.The last case should also apply to #3001180: Use session.lazy-write once Drupal 8 only supports PHP 7.
Comment #57
znerol commentedThanks, left some remarks in the MR. #3001180: Use session.lazy-write once Drupal 8 only supports PHP 7 should be trivial after this landed.
Comment #58
kentr commentedChanges made.
Comment #59
znerol commentedGreat work. Tests are green. Verified manually that no invalid session ids are created in the DB. Thanks a lot @kentr.
Comment #60
kentr commentedDoes this issue still need backport to D7?
Comment #61
greggles@kentr I think some bulk updates are coming to address some issue metadata, but agreed this issue should stay focused on getting the MR into D11 and not worry about D7. Removing the "Needs backport to D7" issue tag.
Comment #62
quietone commentedOf the MRs here which is the one to be reviewed? Kindly add that to the issue summary.
Comment #63
kentr commentedUpdated issue summary to indicate that the MR to be reviewed is !10482.
Comment #64
kentr commentedPutting back to RTBC because it was before #62.
Comment #65
oily commentedPipeline running green except for test-only test which fails as it should.
Comment #66
oily commentedEdited MR, addressed last code comment.
Comment #70
znerol commentedThanks @kentr and @oily, RTBC++
Comment #71
quietone commentedI read the IS, comments, and CR. I skimmed the MR. I fixed wrapping on one comment using the suggestion feature. I didn't find any unanswered questions and I updated credit.
Leaving at RTBC
Comment #73
catchCommitted/pushed to 11.x, thanks!
I think there's enough changes in here that we should probably keep it just in 11.2.