Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit cd65297

Browse filesBrowse files
[HttpFoundation] Make sessions secure and lazy
1 parent d48bcbf commit cd65297
Copy full SHA for cd65297

File tree

11 files changed

+239
-29
lines changed
Filter options

11 files changed

+239
-29
lines changed

‎composer.json

Copy file name to clipboardExpand all lines: composer.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"symfony/polyfill-intl-icu": "~1.0",
3131
"symfony/polyfill-mbstring": "~1.0",
3232
"symfony/polyfill-php56": "~1.0",
33-
"symfony/polyfill-php70": "~1.0",
33+
"symfony/polyfill-php70": "~1.6",
3434
"symfony/polyfill-util": "~1.0"
3535
},
3636
"replace": {

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+3-10Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -908,24 +908,17 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c
908908
}
909909
}
910910

911-
$container->setParameter('session.storage.options', $options);
912-
913911
// session handler (the internal callback registered with PHP session management)
914912
if (null === $config['handler_id']) {
915913
// Set the handler class to be null
916914
$container->getDefinition('session.storage.native')->replaceArgument(1, null);
917915
$container->getDefinition('session.storage.php_bridge')->replaceArgument(0, null);
918916
} else {
919-
$handlerId = $config['handler_id'];
920-
921-
if ($config['metadata_update_threshold'] > 0) {
922-
$container->getDefinition('session.handler.write_check')->addArgument(new Reference($handlerId));
923-
$handlerId = 'session.handler.write_check';
924-
}
925-
926-
$container->setAlias('session.handler', $handlerId)->setPrivate(true);
917+
$options['lazy_write'] = 1;
918+
$container->setAlias('session.handler', $config['handler_id'])->setPrivate(true);
927919
}
928920

921+
$container->setParameter('session.storage.options', $options);
929922
$container->setParameter('session.save_path', $config['save_path']);
930923

931924
if (\PHP_VERSION_ID < 70000) {

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml
+3-1Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@
5252
<argument>%session.save_path%</argument>
5353
</service>
5454

55-
<service id="session.handler.write_check" class="Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler" />
55+
<service id="session.handler.write_check" class="Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler">
56+
<deprecated>The "%service_id%" service is deprecated since Symfony 3.4 and will be removed in 4.0. Use the `session.lazy_write` ini setting instead.</deprecated>
57+
</service>
5658

5759
<service id="session_listener" class="Symfony\Component\HttpKernel\EventListener\SessionListener">
5860
<tag name="kernel.event_subscriber" />
+115Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
13+
14+
/**
15+
* @author Nicolas Grekas <p@tchwork.com>
16+
*/
17+
abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
18+
{
19+
protected $sessionName;
20+
21+
private $prefetchId;
22+
private $prefetchData;
23+
24+
abstract protected function doRead($sessionId);
25+
26+
abstract protected function doWrite($sessionId, $data);
27+
28+
abstract protected function doDestroy($sessionId);
29+
30+
/**
31+
* {@inheritdoc}
32+
*/
33+
public function validateId($sessionId)
34+
{
35+
$this->prefetchId = $sessionId;
36+
$this->prefetchData = $this->doRead($sessionId);
37+
38+
return '' !== $this->prefetchData;
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
public function read($sessionId)
45+
{
46+
if (null !== $this->prefetchId) {
47+
$prefetchId = $this->prefetchId;
48+
$prefetchData = $this->prefetchData;
49+
$this->prefetchId = $this->prefetchData = null;
50+
51+
if ($prefetchId === $sessionId) {
52+
return $prefetchData;
53+
}
54+
}
55+
56+
return $this->doRead($sessionId);
57+
}
58+
59+
/**
60+
* {@inheritdoc}
61+
*/
62+
public function updateTimestamp($sessionId, $data)
63+
{
64+
return $this->write($sessionId, $data);
65+
}
66+
67+
/**
68+
* {@inheritdoc}
69+
*/
70+
public function write($sessionId, $data)
71+
{
72+
if ('' === $data) {
73+
return $this->destroy($sessionId);
74+
}
75+
76+
return $this->doWrite($sessionId, $data);
77+
}
78+
79+
/**
80+
* {@inheritdoc}
81+
*/
82+
public function destroy($sessionId)
83+
{
84+
if (!headers_sent()) {
85+
$sessionCookie = sprintf(' %s=', urlencode($this->sessionName));
86+
$sessionCookieWithId = sprintf('%s%s;', $sessionCookie, urlencode($sessionId));
87+
$sessionCookieFound = false;
88+
$otherCookies = array();
89+
foreach (headers_list() as $h) {
90+
if (0 !== stripos($h, 'Set-Cookie:')) {
91+
continue;
92+
}
93+
if (11 === strpos($h, $sessionCookie, 11)) {
94+
$sessionCookieFound = true;
95+
96+
if (11 !== strpos($h, $sessionCookieWithId, 11)) {
97+
$otherCookies[] = $h;
98+
}
99+
} else {
100+
$otherCookies[] = $h;
101+
}
102+
}
103+
if ($sessionCookieFound) {
104+
header_remove('Set-Cookie');
105+
foreach ($otherCookies as $h) {
106+
header('Set-Cookie:'.$h, false);
107+
}
108+
} else {
109+
setcookie($this->sessionName, '', 0);
110+
}
111+
}
112+
113+
return $this->doDestroy($sessionId);
114+
}
115+
}

‎src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php
+15-5Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*
2020
* @author Drak <drak@zikula.org>
2121
*/
22-
class MemcachedSessionHandler implements \SessionHandlerInterface
22+
class MemcachedSessionHandler extends AbstractSessionHandler
2323
{
2424
/**
2525
* @var \Memcached Memcached driver
@@ -39,7 +39,7 @@ class MemcachedSessionHandler implements \SessionHandlerInterface
3939
/**
4040
* List of available options:
4141
* * prefix: The prefix to use for the memcached keys in order to avoid collision
42-
* * expiretime: The time to live in seconds
42+
* * expiretime: The time to live in seconds.
4343
*
4444
* @param \Memcached $memcached A \Memcached instance
4545
* @param array $options An associative array of Memcached options
@@ -65,6 +65,8 @@ public function __construct(\Memcached $memcached, array $options = array())
6565
*/
6666
public function open($savePath, $sessionName)
6767
{
68+
$this->sessionName = $sessionName;
69+
6870
return true;
6971
}
7072

@@ -79,23 +81,31 @@ public function close()
7981
/**
8082
* {@inheritdoc}
8183
*/
82-
public function read($sessionId)
84+
protected function doRead($sessionId)
8385
{
8486
return $this->memcached->get($this->prefix.$sessionId) ?: '';
8587
}
8688

8789
/**
8890
* {@inheritdoc}
8991
*/
90-
public function write($sessionId, $data)
92+
public function updateTimestamp($sessionId, $data)
93+
{
94+
return $this->memcached->touch($this->prefix.$sessionId, time() + $this->ttl);
95+
}
96+
97+
/**
98+
* {@inheritdoc}
99+
*/
100+
protected function doWrite($sessionId, $data)
91101
{
92102
return $this->memcached->set($this->prefix.$sessionId, $data, time() + $this->ttl);
93103
}
94104

95105
/**
96106
* {@inheritdoc}
97107
*/
98-
public function destroy($sessionId)
108+
protected function doDestroy($sessionId)
99109
{
100110
$result = $this->memcached->delete($this->prefix.$sessionId);
101111

‎src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php
+7-5Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* @see https://packagist.org/packages/mongodb/mongodb
2020
* @see http://php.net/manual/en/set.mongodb.php
2121
*/
22-
class MongoDbSessionHandler implements \SessionHandlerInterface
22+
class MongoDbSessionHandler extends AbstractSessionHandler
2323
{
2424
/**
2525
* @var \Mongo|\MongoClient|\MongoDB\Client
@@ -43,7 +43,7 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
4343
* * id_field: The field name for storing the session id [default: _id]
4444
* * data_field: The field name for storing the session data [default: data]
4545
* * time_field: The field name for storing the timestamp [default: time]
46-
* * expiry_field: The field name for storing the expiry-timestamp [default: expires_at]
46+
* * expiry_field: The field name for storing the expiry-timestamp [default: expires_at].
4747
*
4848
* It is strongly recommended to put an index on the `expiry_field` for
4949
* garbage-collection. Alternatively it's possible to automatically expire
@@ -97,6 +97,8 @@ public function __construct($mongo, array $options)
9797
*/
9898
public function open($savePath, $sessionName)
9999
{
100+
$this->sessionName = $sessionName;
101+
100102
return true;
101103
}
102104

@@ -111,7 +113,7 @@ public function close()
111113
/**
112114
* {@inheritdoc}
113115
*/
114-
public function destroy($sessionId)
116+
protected function doDestroy($sessionId)
115117
{
116118
$methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove';
117119

@@ -139,7 +141,7 @@ public function gc($maxlifetime)
139141
/**
140142
* {@inheritdoc}
141143
*/
142-
public function write($sessionId, $data)
144+
protected function doWrite($sessionId, $data)
143145
{
144146
$expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
145147

@@ -171,7 +173,7 @@ public function write($sessionId, $data)
171173
/**
172174
* {@inheritdoc}
173175
*/
174-
public function read($sessionId)
176+
protected function doRead($sessionId)
175177
{
176178
$dbData = $this->getCollection()->findOne(array(
177179
$this->options['id_field'] => $sessionId,

‎src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php
+10-6Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
* @author Michael Williams <michael.williams@funsational.com>
3939
* @author Tobias Schultze <http://tobion.de>
4040
*/
41-
class PdoSessionHandler implements \SessionHandlerInterface
41+
class PdoSessionHandler extends AbstractSessionHandler
4242
{
4343
/**
4444
* No locking is done. This means sessions are prone to loss of data due to
@@ -260,6 +260,8 @@ public function isSessionExpired()
260260
*/
261261
public function open($savePath, $sessionName)
262262
{
263+
$this->sessionName = $sessionName;
264+
263265
if (null === $this->pdo) {
264266
$this->connect($this->dsn ?: $savePath);
265267
}
@@ -273,7 +275,7 @@ public function open($savePath, $sessionName)
273275
public function read($sessionId)
274276
{
275277
try {
276-
return $this->doRead($sessionId);
278+
return parent::read($sessionId);
277279
} catch (\PDOException $e) {
278280
$this->rollback();
279281

@@ -296,7 +298,7 @@ public function gc($maxlifetime)
296298
/**
297299
* {@inheritdoc}
298300
*/
299-
public function destroy($sessionId)
301+
protected function doDestroy($sessionId)
300302
{
301303
// delete the record associated with this id
302304
$sql = "DELETE FROM $this->table WHERE $this->idCol = :id";
@@ -317,7 +319,7 @@ public function destroy($sessionId)
317319
/**
318320
* {@inheritdoc}
319321
*/
320-
public function write($sessionId, $data)
322+
protected function doWrite($sessionId, $data)
321323
{
322324
$maxlifetime = (int) ini_get('session.gc_maxlifetime');
323325

@@ -491,7 +493,7 @@ private function rollback()
491493
*
492494
* @return string The session data
493495
*/
494-
private function doRead($sessionId)
496+
protected function doRead($sessionId)
495497
{
496498
$this->sessionExpired = false;
497499

@@ -517,7 +519,9 @@ private function doRead($sessionId)
517519
return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0];
518520
}
519521

520-
if (self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
522+
if (\PHP_VERSION_ID < 70000 && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
523+
// Before PHP 7.0, session fixation was possible so locking could be needed even when creating a session.
524+
// Starting with 7.0, secure random ids are generated so not concurrency is possible, thus this code path can be removed.
521525
// Exclusive-reading of non-existent rows does not block, so we need to do an insert to block
522526
// until other connections to the session are committed.
523527
try {

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.