Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ PHP NEWS
. Fixed segfault in ReflectionMethod::createFromMethodName() on an
uninstantiable subclass. (iliaal)

- Session:
. Fixed session.use_strict_mode being a no-op for the built-in
SessionHandler. (iliaal)

- Standard:
. Added the "filter.max_filter_count" stream context option for php://filter
URLs. Using more than 16 filters without configuring this option is now
Expand Down
6 changes: 6 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ PHP 8.6 UPGRADE NOTES
explicitly set this to "None" (and also set session.cookie_secure
to 1).
RFC: https://wiki.php.net/rfc/session_security_defaults
. SessionHandler::validateId() has been added, so session.use_strict_mode
now takes effect for the built-in handler. Subclasses that already
declare validateId() must be signature compatible with
validateId(string $id): bool. A subclass that overrides open() without
calling parent::open() keeps its previous behavior, because the parent
handler it would validate against was never opened.

- Shmop:
. shmop_open() now raises a ValueError when the $key argument is outside the
Expand Down
17 changes: 17 additions & 0 deletions ext/session/mod_user_class.c
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,20 @@ PHP_METHOD(SessionHandler, create_sid)

RETURN_STR(id);
}

PHP_METHOD(SessionHandler, validateId)
{
zend_string *key;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &key) == FAILURE) {
RETURN_THROWS();
}

PS_SANITY_CHECK;

if (!PS(mod_user_is_open)) {
RETURN_TRUE;
}

RETURN_BOOL(SUCCESS == PS(default_mod)->s_validate_sid(&PS(mod_data), key));
}
3 changes: 3 additions & 0 deletions ext/session/session.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,7 @@ public function gc(int $max_lifetime): int|false {}

/** @tentative-return-type */
public function create_sid(): string {}

/** @tentative-return-type */
public function validateId(string $id): bool {}
}
6 changes: 5 additions & 1 deletion ext/session/session_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

127 changes: 127 additions & 0 deletions ext/session/tests/session_strict_handler_validate.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
--TEST--
use_strict_mode distinguishes SessionHandler from custom-storage subclasses
--EXTENSIONS--
session
--INI--
session.use_strict_mode=1
session.use_cookies=0
session.cache_limiter=
session.gc_probability=0
--FILE--
<?php
$save_path = sys_get_temp_dir() . '/sess_strict_' . getmypid();
@mkdir($save_path);
ini_set('session.save_path', $save_path);

$valid_id = 'validfilesessionid';
file_put_contents($save_path . '/sess_' . $valid_id, 'value|s:5:"files";');

session_set_save_handler(new SessionHandler, true);
session_id($valid_id);
session_start();
$session_handler_preserved = session_id() === $valid_id;
$session_handler_loaded = $_SESSION['value'] ?? null;
session_write_close();

$invalid_id = 'attackerchosensessionidyy' . bin2hex(random_bytes(4));
session_id($invalid_id);
session_start();
$session_handler_adopted = session_id() === $invalid_id;
session_write_close();

class CustomStorageHandler extends SessionHandler
{
private array $sessions = [
'valid-custom-session-id' => 'value|s:6:"loaded";',
];

public function open(string $path, string $name): bool
{
return true;
}

public function close(): bool
{
return true;
}

public function read(string $id): string|false
{
return $this->sessions[$id] ?? '';
}

public function write(string $id, string $data): bool
{
$this->sessions[$id] = $data;
return true;
}

public function destroy(string $id): bool
{
unset($this->sessions[$id]);
return true;
}

public function gc(int $max_lifetime): int|false
{
return 0;
}
}

session_set_save_handler(new CustomStorageHandler, true);
$id = 'valid-custom-session-id';
session_id($id);
session_start();
$custom_handler_preserved = session_id() === $id;
$custom_handler_loaded = $_SESSION['value'] ?? null;
session_write_close();

class AugmentingHandler extends SessionHandler
{
public function read(string $id): string|false
{
return parent::read($id);
}
}

session_set_save_handler(new AugmentingHandler, true);
$augmenting_invalid_id = 'attackerchosensessionidzz' . bin2hex(random_bytes(4));
session_id($augmenting_invalid_id);
session_start();
$augmenting_handler_adopted = session_id() === $augmenting_invalid_id;
session_write_close();

session_set_save_handler(new AugmentingHandler, true);
session_id($valid_id);
session_start();
$augmenting_handler_preserved = session_id() === $valid_id;
session_write_close();

foreach (glob($save_path . '/*') as $f) {
@unlink($f);
}
@rmdir($save_path);

echo "SessionHandler preserved: ";
var_dump($session_handler_preserved);
echo "SessionHandler loaded: ";
var_dump($session_handler_loaded);
echo "SessionHandler adopted unknown: ";
var_dump($session_handler_adopted);
echo "Custom handler preserved: ";
var_dump($custom_handler_preserved);
echo "Custom handler loaded: ";
var_dump($custom_handler_loaded);
echo "Augmenting handler adopted unknown: ";
var_dump($augmenting_handler_adopted);
echo "Augmenting handler preserved: ";
var_dump($augmenting_handler_preserved);
?>
--EXPECT--
SessionHandler preserved: bool(true)
SessionHandler loaded: string(5) "files"
SessionHandler adopted unknown: bool(false)
Custom handler preserved: bool(true)
Custom handler loaded: string(6) "loaded"
Augmenting handler adopted unknown: bool(false)
Augmenting handler preserved: bool(true)
Loading