8000 [HttpFoundation] Make sessions secure and lazy by nicolas-grekas · Pull Request #24523 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[HttpFoundation] Make sessions secure and lazy #24523

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 16, 2017

Conversation

nicolas-grekas
Copy link
Member
@nicolas-grekas nicolas-grekas commented Oct 11, 2017
Q A
Branch? 3.4
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? yes
Tests pass? not yet
Fixed tickets #6388, #6036, #12375, #12325
License MIT
Doc PR -

The SessionUpdateTimestampHandlerInterface (new to PHP 7.0) is mostly undocumented, and just not implemented anywhere. Yet, it's required to implement session fixation preventions and lazy write in userland session handlers (there is https://wiki.php.net/rfc/session-read_only-lazy_write which describes the behavior.)

By implementing it, we would make Symfony session handling much better and stronger. Meanwhile, doing some cookie headers management, this also gives the opportunity to fix the "don't start if session is only read issue".

So, here we are for the general idea. Now needs more (and green) tests, and review of course.

@eXtreme
Copy link
Contributor
eXtreme commented Oct 11, 2017

wow, there's literally nothing on google about SessionUpdateTimestampHandlerInterface, yet it exists for so long ..

@nicolas-grekas nicolas-grekas force-pushed the session-remove-on-empty branch from cd65297 to 090a3a7 Compare October 12, 2017 07:37
if (self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
if (\PHP_VERSION_ID < 70000 && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
// Before PHP 7.0, session fixation was possible so locking could be needed even when creating a session.
// Starting with 7.0, secure random ids are generated so not concurrency is possible, thus this code path can be removed.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Tobion do you agree with this statement?

@nicolas-grekas nicolas-grekas force-pushed the session-remove-on-empty branch 4 times, most recently from 6dcecf3 to f900f1b Compare October 12, 2017 09:59
<argument>%session.save_path%</argument>
<service id="session.handler.native_file" class="Symfony\Component\HttpFoundation\Session\Storage\Handler\SessionHandlerProxy">
<argument type="service">
<service class="Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler">
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for some obscure reason, the native SessionHandler doesn't implement SessionUpdateTimestampHandlerInterface
we have to proxy it to add strict mode and lazy writes...

@nicolas-grekas nicolas-grekas force-pushed the session-remove-on-empty branch 4 times, most recently from aa5e67b to aa03463 Compare October 12, 2017 15:09
@nicolas-grekas
Copy link
Member Author
nicolas-grekas commented Oct 12, 2017

PR is green and ready. This is a significant improvement over the current session handlers, thanks to this new PHP 7.0 interface.

This adds two main classes:

  • AbstractSessionHandler, which implements the logic to remove/delete cookies when needed + deal with validateId()+updateTimestamp()
  • SessionHandlerProxy, which turns any handler into an instance of AbstractSessionHandler, especially useful and required for the native session handler, which doesn't implement the special interface.

The rest are related tweaks.

@TerjeBr
Copy link
TerjeBr commented Oct 12, 2017

Have you tested this with a full symfony stack?

When a typical symfony SessionBag is empty it is not the empty string, it contains some metadata.
A new session that is started with no data will contain an array with these 3 keys: _sf2_attributes, _sf2_flashes and _sf2_meta

Because of this, this PR will not fix #6388

1 => array('file', '/dev/null', 'w'),
2 => array('file', '/dev/null', 'w'),
);
if (!self::$server = @proc_open('exec php -S localhost:8053', $spec, $pipes, __DIR__.'/Fixtures')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exec won't work on windows

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, on purpose: signaling the php -S process to kill it is complex, so this is skipped on Windows as I don't want to import the Process component just for this.

@nicolas-grekas nicolas-grekas force-pushed the session-remove-on-empty branch from 7a898e9 to b75cc85 Compare October 12, 2017 21:19
@@ -4,6 +4,7 @@ CHANGELOG
3.4.0
-----

* Session `use_strict_mode` is now enabled by default and the corresponding option has been deprecated,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra comma at the end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there is a missing counterpart of the CHANGELOG in UPGRADE-4.0.md

8000
Copy link
Contributor
@Tobion Tobion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work @nicolas-grekas. Improving the session system was long overdue and I like how you solved it.

}

$container->setAlias('session.handler', $handlerId)->setPrivate(true);
$options['lazy_write'] = 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is lazy write only enabled in this case but not for native session handlers? Seems arbitrary as we don't know more about a custom session handler than we do about the native one. So lazy_write should be safe to be enabled all the time.

@@ -401,11 +408,13 @@ public function setSaveHandler($saveHandler = null)
if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) {
$saveHandler = new SessionHandlerProxy($saveHandler);
} elseif (!$saveHandler instanceof AbstractProxy) {
$saveHandler = new SessionHandlerProxy(new \SessionHandler());
$saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler()));
Copy link
Contributor
@Tobion Tobion Oct 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels strange that we need to make the native session handlers strict. The files session handler is already strict I assume. So this is only needed because if you use \SessionHandler, you lose that strictness? \SessionHandler does not implement \SessionUpdateTimestampHandlerInterface! So if we not not set a save handler at all it would even be better as it would then use the validate id implementation of the native session handler? But currently that is not possible do.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, I don't think we can do better here, id validation works with the wrapper so not a big issue. There is only updateTimestamp which ships no optimization but only writes. Not sure we can do better.

Copy link
Contributor
@Tobion Tobion Oct 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If php doesn't change this behavior, we can think about deprecating NativeFilesSessionHandler in SF 4.1. So by default it just sets the session.save_handler ini without actually overwriting \SessionHandler.

public function read($sessionId)
public function updateTimestamp($sessionId, $data)
{
$expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is duplicated code from function doWrite (line 144). Would it not be better to put this common code in a new protected method? May be something like createExpiryTime.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not worth it for one line IMHO

@nicolas-grekas nicolas-grekas force-pushed the session-remove-on-empty branch from ef7cdc2 to 347939c Compare October 16, 2017 22:30
@nicolas-grekas
Copy link
Member Author

comments addressed

@fabpot
Copy link
Member
fabpot commented Oct 16, 2017

Thank you @nicolas-grekas.

@fabpot fabpot merged commit 347939c into symfony:3.4 Oct 16, 2017
fabpot added a commit that referenced this pull request Oct 16, 2017
…s-grekas)

This PR was merged into the 3.4 branch.

Discussion
----------

[HttpFoundation] Make sessions secure and lazy

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | not yet
| Fixed tickets | #6388, #6036, #12375, #12325
| License       | MIT
| Doc PR        | -

The `SessionUpdateTimestampHandlerInterface` (new to PHP 7.0) is mostly undocumented, and just not implemented anywhere. Yet, it's required to implement session fixation preventions and lazy write in userland session handlers (there is https://wiki.php.net/rfc/session-read_only-lazy_write which describes the behavior.)

By implementing it, we would make Symfony session handling much better and stronger. Meanwhile, doing some cookie headers management, this also gives the opportunity to fix the "don't start if session is only read issue".

So, here we are for the general idea. Now needs more (and green) tests, and review of course.

Commits
-------

347939c [HttpFoundation] Make sessions secure and lazy
@nicolas-grekas nicolas-grekas deleted the session-remove-on-empty branch October 16, 2017 23:11
@Tobion
Copy link
Contributor
Tobion commented Oct 16, 2017

@nicolas-grekas what I proposed in #12325 is to not start the session at all until data is set when there is no session cookie in the request. I don't think that it implemented yet. This would avoid generating a session id, starting the session handler etc when you just read session data to then realize nothing is there.

@nicolas-grekas
Copy link
Member Author

@Tobion from the HTTP pov, the observed behavior is exactly the same. In fact, starting the session has the benefit of sending the appropriate Cache-Control header. If HTTP cacheability is improved by the current change, it means we may not have to care anymore about whether the session is really started or not.

@Tobion
Copy link
Contributor
Tobion commented Oct 16, 2017

If I just want to check if the user is logged in to then redirect or whatever, I don't need to start the session if there is no session cookie. And a cache control header might not be what I want.

This was referenced Oct 18, 2017
fabpot added a commit that referenced this pull request Oct 19, 2017
This PR was merged into the 4.0-dev branch.

Discussion
----------

[Session] remove lazy_write polyfill for php < 7.0

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no <!-- don't forget to update src/**/CHANGELOG.md files -->
| BC breaks?    |no
| Deprecations? | no <!-- don't forget to update UPGRADE-*.md files -->
| Tests pass?   | yes
| Fixed tickets |
| License       | MIT
| Doc PR        |

Remove the session.lazy_write fallback implementation for php < 7 introduced in #24523 as we don't need it in sf 4

Commits
-------

1f84b1f [Session] remove lazy_write polyfill for php < 7.0
ddeboer added a commit to driebit/SessionStorageHandlerChainBundle that referenced this pull request Dec 3, 2017
* Make compatible with  symfony/symfony#24523.
* Open session in all readers/writers to fix error
  during session_regenerate_id.
* Return boolean from close(), which Symfony expects.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants
0