我编写了当前投票数最高的评论,并想添加一些内容。我之前评论中现有的代码以不安全的方式生成其随机数 -
<?php
$_SESSION['nonce'] = md5(microtime(true));
?>
由于“microtime”是可预测的,因此它使暴力破解随机数变得更容易。更好的选择是使用随机性的方法,例如 -
<?php
bin2hex(openssl_random_pseudo_bytes(32))
?>
(PHP 4 >= 4.3.2, PHP 5, PHP 7, PHP 8)
session_regenerate_id — 使用新生成的 ID 更新当前会话 ID
session_regenerate_id() 将使用新的会话 ID 替换当前会话 ID,并保留当前会话信息。
当 session.use_trans_sid 启用时,必须在 session_regenerate_id() 调用后启动输出。否则,将使用旧的会话 ID。
目前,session_regenerate_id 无法很好地处理不稳定的网络,例如移动和 WiFi 网络。因此,您可能会在调用 session_regenerate_id 时遇到会话丢失的情况。
您不应该立即销毁旧的会话数据,而应该使用销毁时间戳并控制对旧会话 ID 的访问。否则,并发访问页面可能会导致状态不一致,或者您可能会丢失会话,或者它可能会导致客户端(浏览器)端竞争条件并可能无谓地创建许多会话 ID。立即删除会话数据也会禁用会话劫持攻击检测和预防。
delete_old_session
是否删除旧的关联会话文件。如果您需要避免因删除或检测/避免会话劫持攻击而引起的竞争条件,则不应删除旧会话。
示例 #1 session_regenerate_id() 示例
<?php
// 注意:此代码不是完整的可运行代码,而是一个示例!
session_start();
// 检查已销毁的时间戳
if (isset($_SESSION['destroyed'])
&& $_SESSION['destroyed'] < time() - 300) {
// 通常情况下不应该发生。这可能是攻击或由于网络不稳定。
// 删除此用户会话的所有身份验证状态。
remove_all_authentication_flag_from_active_sessions($_SESSION['userid']);
throw(new DestroyedSessionAccessException);
}
$old_sessionid = session_id();
// 设置销毁时间戳
$_SESSION['destroyed'] = time(); // session_regenerate_id() 保存旧的会话数据
// 仅仅调用 session_regenerate_id() 可能会导致会话丢失等。
// 请参见下一个示例。
session_regenerate_id();
// 新的会话不需要销毁时间戳
unset($_SESSION['destroyed']);
$new_sessionid = session_id();
echo "旧会话:$old_sessionid<br />";
echo "新会话:$new_sessionid<br />";
print_r($_SESSION);
?>
当前的会话模块无法很好地处理不稳定的网络。您应该管理会话 ID 以避免 session_regenerate_id 导致会话丢失。
示例 #2 通过 session_regenerate_id() 避免会话丢失
<?php
// 注意:此代码不是完整的可运行代码,而是一个示例!
// my_session_start() 和 my_session_regenerate_id() 通过
// 不稳定的网络避免会话丢失。此外,此代码还可以防止攻击者利用被盗
// 会话。
function my_session_start() {
session_start();
if (isset($_SESSION['destroyed'])) {
if ($_SESSION['destroyed'] < time()-300) {
// 通常情况下不应该发生。这可能是攻击或由于网络不稳定。
// 删除此用户会话的所有身份验证状态。
remove_all_authentication_flag_from_active_sessions($_SESSION['userid']);
throw(new DestroyedSessionAccessException);
}
if (isset($_SESSION['new_session_id'])) {
// 尚未完全过期。可能是由于网络不稳定导致 Cookie 丢失。
// 再次尝试设置正确的会话 ID Cookie。
// 注意:如果您想删除身份验证标志,请不要尝试再次设置会话 ID。
session_commit();
session_id($_SESSION['new_session_id']);
// 新的会话 ID 应该存在
session_start();
return;
}
}
}
function my_session_regenerate_id() {
// 当由于网络不稳定导致会话 ID 未设置时,需要新的会话 ID 来设置正确的会话 ID。
$new_session_id = session_create_id();
$_SESSION['new_session_id'] = $new_session_id;
// 设置销毁时间戳
$_SESSION['destroyed'] = time();
// 写入并关闭当前会话;
session_commit();
// 使用新的会话 ID 启动会话
session_id($new_session_id);
ini_set('session.use_strict_mode', 0);
session_start();
ini_set('session.use_strict_mode', 1);
// 新的会话不需要它们
unset($_SESSION['destroyed']);
unset($_SESSION['new_session_id']);
}
?>
我编写了当前投票数最高的评论,并想添加一些内容。我之前评论中现有的代码以不安全的方式生成其随机数 -
<?php
$_SESSION['nonce'] = md5(microtime(true));
?>
由于“microtime”是可预测的,因此它使暴力破解随机数变得更容易。更好的选择是使用随机性的方法,例如 -
<?php
bin2hex(openssl_random_pseudo_bytes(32))
?>
我为正在开发的一个项目编写了以下代码 - 它尝试解决重新生成问题,并处理其他一些与会话相关的事情。
我尝试使其更通用和可用(例如,在完整版本中,它为不同类型的会话问题抛出不同类型的异常),因此希望有人会发现它有用。
<?php
function regenerateSession($reload = false)
{
// 此令牌由表单用于防止跨站点伪造请求
if(!isset($_SESSION['nonce']) || $reload)
$_SESSION['nonce'] = md5(microtime(true));
if(!isset($_SESSION['IPaddress']) || $reload)
$_SESSION['IPaddress'] = $_SERVER['REMOTE_ADDR'];
if(!isset($_SESSION['userAgent']) || $reload)
$_SESSION['userAgent'] = $_SERVER['HTTP_USER_AGENT'];
//$_SESSION['user_id'] = $this->user->getId();
// 设置当前会话在 1 分钟后过期
$_SESSION['OBSOLETE'] = true;
$_SESSION['EXPIRES'] = time() + 60;
// 创建新会话而不销毁旧会话
session_regenerate_id(false);
// 获取当前会话 ID 并关闭两个会话以允许其他脚本使用它们
$newSession = session_id();
session_write_close();
// 将会话 ID 设置为新的会话 ID,并重新启动它
session_id($newSession);
session_start();
// 不希望此会话过期
unset($_SESSION['OBSOLETE']);
unset($_SESSION['EXPIRES']);
}
function checkSession()
{
try{
if($_SESSION['OBSOLETE'] && ($_SESSION['EXPIRES'] < time()))
throw new Exception('Attempt to use expired session.');
if(!is_numeric($_SESSION['user_id']))
throw new Exception('No session started.');
if($_SESSION['IPaddress'] != $_SERVER['REMOTE_ADDR'])
throw new Exception('IP Address mixmatch (possible session hijacking attempt).');
if($_SESSION['userAgent'] != $_SERVER['HTTP_USER_AGENT'])
throw new Exception('Useragent mixmatch (possible session hijacking attempt).');
if(!$this->loadUser($_SESSION['user_id']))
throw new Exception('Attempted to log in user that does not exist with ID: ' . $_SESSION['user_id']);
if(!$_SESSION['OBSOLETE'] && mt_rand(1, 100) == 1)
{
$this->regenerateSession();
}
return true;
}catch(Exception $e){
return false;
}
}
?>
在 PHP 5.6(可能还有更旧的版本)中,session_regenerate_id(true) 不会触发对新会话 ID 的会话处理程序的 read() 调用。
在 PHP 7 中,在 session_regenerate_id(true) 期间会触发 read()。在使用自定义会话处理程序时了解这一点很有用。
如果您尝试维护 2 个活动会话,请不要使用 session_regenerate_id()。特别是如果第一个会话已关闭并且是打开第二个会话的时候。由于会话 ID 缓存在缓存中,因此您还必须在第二次显式设置它。
<?php
session_name('PHPSESSID'); // 多余的 - 为清晰起见
session_start();
// ...执行操作
session_write_close();
// 现在切换到会话 2...
session_name('PHPSESSID_2');
if (isset($_COOKIE['phpsessid_2']))
session_id($_COOKIE['phpsessid_2']); // 不执行此操作将简单地重新打开第一个会话
else
session_id(sha1(mt_rand()); // 不要在这里使用 session_regenerate_id()。不创建新的 ID 将创建两个具有相同会话 ID 和相同会话变量的 Cookie
session_start();
// ...使用会话 2 执行操作
session_write_close();
?>
在之前的注释中,php at 5mm de 描述了如何通过
确保提供的会话 ID 与首次发出会话 ID 时存在的 HTTP_USER_AGENT 和 REMOTE_ADDR 字段匹配来防止会话劫持。需要注意的是,HTTP_USER_AGENT 由客户端提供,因此恶意用户可以轻松修改。此外,客户端 IP 地址可能会被欺骗,尽管这有点困难。在依赖会话进行身份验证时应谨慎。
`session_regenerate_id` 会发送一个新的 Cookie,但不会覆盖存储在 `$_COOKIE` 中的值。在调用 `session_destroy` 后,打开的会话 ID 会被丢弃,因此,仅仅使用 `session_start` 重新启动会话(如 Ben Johnson 的代码中所做的那样)将在当前请求中重新打开原始的(现在为空的)会话(后续请求将使用新的会话 ID)。不要使用 `session_destroy` + `session_start`,而是使用 `session_regenerate_id` 的 `$delete_old_session` 参数来删除以前的会话数据。
<?php
session_start();
/* 创建一个新的会话,删除以前的会话数据。 */
session_regenerate_id(TRUE);
/* 清除从上一个会话中传递过来的数据 */
$_SESSION=array();
?>
要启动一个新的会话并保持旧的会话不变,只需省略 `session_regenerate_id` 的参数即可。
补充 php at 5mm de 的评论
如果会话通过 https 保持,最好保存客户端的证书或 ssl 会话 ID,而不是主机名或 IP,因为它对代理是透明的,并且更安全。
根据 RFC,文档示例在使用 "session.use_strict_mode" 时是错误的(在 https://wiki.php.net/rfc/strict_sessions 上说:“当 use_strice_mode=1 时,session_id() 会发出警告错误”)。
因此,此指令影响 "session_id()" 而不是 "session_start()"。因此,用法必须像这样;
<?php
// 首先设置 ini
ini_set('session.use_strict_mode', '0');
// 然后
session_id($sid);
// 之后
// 可以运行此操作:ini_restore('session.use_strict_mode');
// 然后继续...
?>
参考(ctrl+f & use_strict_mode);
https://wiki.php.net/rfc/strict_sessions
https://wiki.php.net/rfc/session-create-id
https://php.net/manual/en/function.session-id.php#119997
我在代理更改访问者的 session_id-cookie 时遇到了问题,因此当他访问我的网站时会收到很多错误。
我这样处理了伪造的 session-id。(注意:它仅适用于 4.3.2 以上版本。)
<?php
// 启动会话并抑制错误消息。
@session_start();
// 捕获伪造的 session-id。
if (!preg_match("/^[0-9a-z]*$/i", session_id())) {
// 输出有关乱序 session-id 的警告。
$error->handleError("WARN", "您的会话 ID 出现问题,您可能无法使用此网站上的某些功能。");
// 生成一个新的 session-id。
session_regenerate_id();
}
// 网站内容。
?>
希望有人能用上它。
Session_destroy() 不仅会销毁与当前 session_id 关联的数据(即,如果您使用默认的会话保存处理程序,则会销毁文件),还会销毁会话本身:如果您调用 session_destroy() 然后调用 session_regenerate_id(),它将返回 false,并且 session_id() 不会返回任何内容。为了在销毁会话后操作它,您需要重新启动它。
因此,实际上,chris 提到的代码将不起作用。如果您想销毁与旧 session_id 关联的文件,请尝试以下操作
<?php
session_start();
$old_sessid = session_id();
session_regenerate_id();
$new_sessid = session_id();
session_id($old_sessid);
session_destroy();
//如果您没有复制 $_SESSION 数组,则将无法使用与旧 session id 关联的数据。
$old_session = $_SESSION;
session_id($new_sessid);
session_start();
$_SESSION = $old_session;
//...
?>
注意:此技术将发送 3 个 Set-Cookie 标头(每个 session_start() 一个,session_regenerate_id() 一个)。我认为这不是问题,但如果确实出现问题,您可以选择不理会它并等待垃圾回收器捕获与旧会话关联的文件,或者尝试使用 unlink() 删除该文件。
提供的 my_session_regenerate_id() 代码示例不起作用,并且会销毁所有会话变量。此外,第二个 ini_set 会导致错误。
用于重新生成的代码(仅该部分,其余部分似乎没问题)应该只是这样
<?php
function my_session_regenerate_id() {
$new_session_id = session_create_id();
// 备份会话变量
$keepSession = $_SESSION ;
// 为连接不良且未收到新会话 ID 的用户添加信息
$_SESSION['new_session_id'] = $new_session_id;
// 设置销毁时间戳
$_SESSION['destroyed'] = time();
// 写入并关闭当前会话;
session_commit() ;
// 使用新的会话 ID 启动会话
ini_set('session.use_strict_mode', 0);
session_id($new_session_id);
session_start();
$_SESSION = $keepSession ;
}
?>
如果您不小心处理方式,此函数可能非常危险,因为即使它生成了一整套新的会话数据,它也会将旧数据“打开”直到脚本终止,从而锁定任何尝试使用旧会话 ID 并发运行的其他脚本。
最近我遇到了一种情况,我想显式传递一个会话 ID,将该会话中的数据复制到一个*新*会话中,然后在该新会话下继续操作,从而允许其他脚本并发使用旧会话。但我很快发现,这些“其他脚本”在第一个脚本完成之前不会执行——即使它已经启动了一个新会话——因为它保持旧会话处于打开状态。
因此,如果您尝试将会话数据复制到新会话以释放旧会话以供继续并发使用,以下是一些代码以确保没有人踩到别人的脚
<?php
// 获取现有会话的会话 ID
$sid = $_GET['sid'];
// 启动旧会话以检索 $_SESSION 数据
session_id($sid);
session_start();
// 启动一个新的会话;这会将 $_SESSION 数据复制过去
session_regenerate_id();
// 保留新的会话 ID
$sid = session_id();
// 关闭旧会话和新会话
session_write_close();
// 重新打开新会话
session_id($sid);
session_start();
/* 主要代码在此处 */
?>
如果这是一个重复的操作,可以将其封装到一个只有一个参数的函数中以节省空间。
如果将会话数据存储在数据库中,则必须手动更新数据库中的 session_id。session_set_save_handler() 不会为您执行此操作。
function UpdateSessID() {
$old_sess_id = session_id();
session_regenerate_id(false);
$new_sess_id = session_id();
$query = "UPDATE `session_table` SET `session_id` = '$new_sess_id' WHERE session_id = '$old_sess_id'";
mysql_query($query);
}
请确保将 session_regenerate_id() 设置为 FALSE,因为删除 MySQL 中的整个记录并再次添加它实际上没有必要。这是不必要的开销。只需要更改 ID 即可。
elger at NOSPAM dot yellowbee dot nl 在下面的一些帖子中可能存在潜在问题。代码中使用了 REQUEST_URI 服务器变量,在某些情况下它可能已经包含了查询字符串。因此,始终附加 '?whatever=foo' 偶尔会导致脚本出现故障。我建议使用 PHP_SELF,它不会包含文件后面的查询字符串。
请注意,在当前的 PHP 7.2 nightly 版本中,上面的示例 #2 将无法按所示工作。尝试在 session_start() 后重新启用严格模式时,您将收到以下错误
"ini_set(): A session is active. You cannot change the session module's ini settings at this time"
我想这意味着对于任何执行会话 ID 重新生成或会话 ID 转发(从最近执行了会话 ID 重新生成的会话到新的会话 ID)的会话,您都必须接受在该活动会话的剩余时间内禁用严格模式。
只要您遵循每个请求一个会话的设计(即,您不使用多个并发会话),我认为这并不是真正的安全问题。
licp - 不,session_regenerate_id() 不会销毁任何已保存的会话数据。
elger,我更喜欢以下顺序
[代码]
// 使用当前 session_id 填充 $_SESSION 中的任何先前保存的会话数据
session_start();
...
// 删除与当前 session_id 关联的任何保存的数据,$_SESSION 不变
session_destroy();
// 更改 session_id,$_SESSION 不变
session_regenerate_id();
...
// 在当前 session_id 下保存任何 $_SESSION 数据
session_close();
[/代码]
<?php
function my_session_regenerate_id() {
...
session_start();
...
unset($_SESSION['destroyed']);
unset($_SESSION['new_session_id']);
}
?>
在 my_session_regenerate_id() 中,unset($_SESSION['destroyed']) 和
unset($_SESSION['new_session_id']) 是无用的,因为 session_start() 将打开一个空会话。如果调用内置的 session_regenerate_id(),则需要它们。
据我从网上阅读的笔记中理解,
session_name() 是通过 Cookie 或 HTTP 链接识别为会话 a 的名称。
session_id 就像 session_name() 内的一个事务,一个 session_name 可以有多个 session_id
每个 session_id 都有相应的数据存储。
session_id 用于 session_name 下的读写回调。
无论如何,首先调用
session_name(),
然后调用 session_id()
然后调用 start_session()
start_session 将打开 session_name,然后检查之前调用的 session_id 并将其用于存储或检索数据的读写回调。
在没有 session_name 或 session_id 的情况下调用 start_session() 将按顺序使用默认的 session_name 和默认的 session_id。
我希望如果按照此顺序操作,应该不会出现任何问题。
如果您需要使用 session_name 或 session_id,请不要在 start_session() 之后调用它们。
感谢其他人的笔记。
关于丢失的会话并尝试使用 session_regenerate_id 修复它的说明
确保您没有尝试将 SimpleXML 对象推送到会话中。在将其转换为数组之前,它不会生效。:)
对于 php 5.1> 用户可能值得访问的是 http://ilia.ws/archives/47-session_regenerate_id-Improvement.html