Session fixation for anonymous users - discard invalid session identifiers instead of accepting them

Created on 8 December 2015, about 9 years ago
Updated 5 August 2023, over 1 year ago

Problem/Motivation

If a user sends us a session cookie that contains a session ID that we do not have in our database we should throw away their session id and give them a new one. But we don't.

Steps to reproduce

  1. Install Drupal 8
  2. Install a module that adds some information to the session for anonymous users (outdated but e.g. Sessions Everywhere, https://www.drupal.org/sandbox/kscheirer/2629288 β†’ )
  3. Visit site as an anonymous user, check sessions table
  4. Truncate sessions table
  5. Visit site again, same session id
  6. Truncate sessions table
  7. Modify session value and site again. modified session id stored and kept.

Proposed resolution

If a user comes back with an sid that we don't have in the sessions table, we should create a new session ID for them and send that cookie back to them.

User interface changes

None.

API changes

None.

πŸ› Bug report
Status

Needs work

Version

11.0 πŸ”₯

Component
BaseΒ  β†’

Last updated about 5 hours ago

Created by

πŸ‡ΊπŸ‡ΈUnited States kscheirer Vallejo

Live updates comments and jobs are added and updated live.
  • Needs backport to D7

    After being applied to the 8.x branch, it should be considered for backport to the 7.x branch. Note: This tag should generally remain even after the backport has been written, approved, and committed.

  • Needs tests

    The change is currently missing an automated test that fails when run with the original code, and succeeds when the bug has been fixed.

  • Security improvements

    It makes Drupal less vulnerable to abuse or misuse. Note, this is the preferred tag, though the Security tag has a large body of issues tagged to it. Do NOT publicly disclose security vulnerabilities; contact the security team instead. Anyone (whether security team or not) can apply this tag to security improvements that do not directly present a vulnerability e.g. hardening an API to add filtering to reduce a common mistake in contributed modules.

Sign in to follow issues

Merge Requests

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • πŸ‡«πŸ‡·France andypost

    According to #1561866-98: Add support for built-in PHP session upload progress β†’ upload progress may need separate cookie

  • πŸ‡«πŸ‡·France andypost

    and new MR for 11.x

  • Assigned to kentr
  • πŸ‡ΊπŸ‡ΈUnited States kentr
  • Pipeline finished with Failed
    about 2 months ago
    Total: 172s
    #361689
  • πŸ‡ΊπŸ‡ΈUnited States kentr

    There's an MR for review. I commented on the MR in Gitlab.

  • πŸ‡¨πŸ‡­Switzerland znerol

    Thanks @kentr for picking this up (and thanks @neclimdul for the initial work). In the mean time [#15222090] and πŸ“Œ Add return types to SessionHandler Active landed, so we do not have any typing related BC problems here anymore.

    I'll stick around for reviews, let's land this.

  • πŸ‡³πŸ‡±Netherlands bbrala Netherlands

    Review was done, this added feedback. Setting to needs work.

    Also; reroll no longer needed and there are tests. Removing tags.

  • πŸ‡³πŸ‡±Netherlands bbrala Netherlands
  • πŸ‡ΊπŸ‡ΈUnited States kentr
  • Pipeline finished with Failed
    about 2 months ago
    Total: 183s
    #362054
  • Pipeline finished with Canceled
    about 2 months ago
    Total: 131s
    #362078
  • Pipeline finished with Failed
    about 2 months ago
    Total: 596s
    #362079
  • πŸ‡ΊπŸ‡ΈUnited States kentr
  • πŸ‡ΊπŸ‡ΈUnited States kentr

    Suggestions applied. There are a few outstanding threads.

  • πŸ‡¨πŸ‡­Switzerland znerol

    Test result of the failing test:

    Drupal\Tests\system\Functional\Session\SessionTest::testSessionWrite
    Sessions table was not updated.
    Failed asserting that two strings are equal.
    --- Expected
    +++ Actual
    @@ @@
    -'1733600778'
    +'1733600779'
    
    core/modules/system/tests/src/Functional/Session/SessionTest.php:346
    

    This is in testSessionWrite().

    My hunch is that now that SessionHandler implements updateTimestamp(), the testSessionWrite() 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 the timestamp column.

  • πŸ‡¨πŸ‡­Switzerland znerol

    I've been researching this a little bit. We have the following mechanism in core already:

    1. Drupal core has the session_write_interval setting which defaults to 180 seconds.
    2. That setting is used in core MetadataBag to initialize its parent.
    3. The Symfony MetadataBag will update its timestamp when the update threshold is reached.
    4. When that happens, session metadata is changed and SessionHandler:write() is called when the request terminates.
    5. That in turn will also update the timestamp field.

    Hence, in order to avoid unnecessary writes, the updateTimestamp() method must not touch the database. Instead it should just return FALSE;.

  • πŸ‡ΊπŸ‡ΈUnited States kentr

    Returning FALSE from updateTimestamp() 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 return TRUE to be seen as a successful no-op.

    In Watchdog:

    Warning: session_write_close(): Failed to write session data with "Drupal\session_test\Session\TestSessionHandlerProxy" handler in Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage->Symfony\Component\HttpFoundation\Session\Storage\{closure}() (line 232 of /var/www/html/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php)
    

    In tests:

    Exception: Warning: session_write_close(): Failed to write session data with "Drupal\session_test\Session\TestSessionHandlerProxy" handler
    Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage->Symfony\Component\HttpFoundation\Session\Storage\{closure}()() (Line: 232)
    
  • Pipeline finished with Failed
    about 1 month ago
    Total: 564s
    #362631
  • πŸ‡¨πŸ‡­Switzerland znerol

    I pushed it so that others can observe, but I believe updateTimestamp() should return TRUE to be seen as a successful no-op.

    Yes, you are right. A no-op implementation of updateTimestamp() needs to return TRUE.

  • πŸ‡ΊπŸ‡ΈUnited States kentr

    Yes, you are right. A no-op implementation of updateTimestamp() needs to return TRUE.

    Ok. I changed updateTimestamp() to return TRUE.

  • Pipeline finished with Failed
    about 1 month ago
    Total: 690s
    #362703
  • πŸ‡¨πŸ‡­Switzerland znerol

    There is one test failure:

    Drupal\Tests\block\Functional\BlockTest::testBlockAccess
    Behat\Mink\Exception\ResponseTextException: The text "Hello test world" was not found anywhere in the text of the current page.
    
    vendor/behat/mink/src/WebAssert.php:907
    vendor/behat/mink/src/WebAssert.php:293
    core/tests/Drupal/Tests/WebAssert.php:979
    core/modules/block/tests/src/Functional/BlockTest.php:581

    I've seen that failing randomly in other MRs. Triggered a rerun in order to check whether this is reproducible.

  • πŸ‡¨πŸ‡­Switzerland znerol

    Tests passed, including the one which failed before:

    Drupal\Tests\block\Functional\BlockTest                       14 passes   80s
    
  • πŸ‡¨πŸ‡­Switzerland znerol

    Fantastic 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 validateId before read and updateTimestamp instead of write.

    In order to produce such a trace, it would be necessary to extend the SessionTestController.php with a method similar to traceHandler(), maybe asserting that $_SESSION['trace-handler'] is non-zero but without changing it. The StackSessionHandlerIntegrationTest::testRequest then could subsequently call into that and assert that the new methods on TestSessionHandlerProxy are actually invoked.

  • πŸ‡¨πŸ‡­Switzerland znerol

    Added a CR draft.

  • πŸ‡ΊπŸ‡ΈUnited States kentr

    regarding {@inheritdoc}...let's leave this up to a core committer to decide.

    Got it. I thought it was OK because the phpDoc documentation gives examples of providing additional details along with {@inheritdoc}.

    [StackSessionHandlerIntegrationTest] doesn't cover the situation where the new methods are called from within the php session subsystem

    I'll look into these tests.

  • Pipeline finished with Failed
    about 1 month ago
    Total: 146s
    #363827
  • Pipeline finished with Success
    about 1 month ago
    Total: 1826s
    #363830
  • πŸ‡ΊπŸ‡ΈUnited States kentr

    My 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:

    1. The previously-stored session is read by a request with the corresponding session cookie.
      • validateId() is invoked.
      • write() is not invoked.
      • updateTimestamp() is not invoked.
    2. The previously-stored session is modified by a request with the corresponding session cookie.
      • validateId() is invoked.
      • write() is invoked.
      • updateTimestamp() is not invoked.
    3. The previously-stored session is re-saved as-is (unmodified) by a request with the corresponding session cookie.
      • validateId() is invoked.
      • updateTimestamp() is invoked.
      • write() is not invoked.

    The last case should also apply to πŸ“Œ Use session.lazy-write once Drupal 8 only supports PHP 7 Active .

  • πŸ‡¨πŸ‡­Switzerland znerol

    Thanks, left some remarks in the MR. πŸ“Œ Use session.lazy-write once Drupal 8 only supports PHP 7 Active should be trivial after this landed.

  • Pipeline finished with Failed
    about 1 month ago
    Total: 522s
    #364629
  • Pipeline finished with Success
    about 1 month ago
    Total: 630s
    #364670
  • πŸ‡ΊπŸ‡ΈUnited States kentr

    Changes made.

  • πŸ‡¨πŸ‡­Switzerland znerol

    Great work. Tests are green. Verified manually that no invalid session ids are created in the DB. Thanks a lot @kentr.

  • πŸ‡ΊπŸ‡ΈUnited States kentr

    Does this issue still need backport to D7?

    Drupal core version 7 has reached end of life, and is no longer community supported on Drupal.org.

  • πŸ‡ΊπŸ‡ΈUnited States greggles Denver, Colorado, USA

    @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.

Production build 0.71.5 2024