我在此编写了当前投票最高的评论,并想添加一些内容。我之前评论中存在的代码以不安全的方式生成其随机数 -
<?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 Session: $old_sessionid<br />";
echo "New Session: $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 中,read() 在 session_regenerate_id(true) 期间被触发。在使用自定义会话处理程序时,了解这一点很有用。
如果您尝试维护 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(在 https://wiki.php.net/rfc/strict_sessions 上说明:“当 use_strice_mode=1 时,session_id() 会发出警告错误”),文档示例中“session.use_strict_mode”的用法是错误的。
因此,此指令影响的是“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 数组,您将无法使用与旧会话 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() 代码示例不工作,并且会 DESTROY 所有会话变量。另外,第二个 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 在 NOSPAM 点 yellowbee 点 nl 下面的几篇文章中可能存在潜在问题。在代码中,使用了 REQUEST_URI 服务器变量,它在某些情况下可能已经包含了查询字符串。因此,始终追加 '?whatever=foo' 有时会导致脚本出现故障。我建议使用 PHP_SELF,它不会在文件之后包含查询字符串。
请注意,在当前的 PHP 7.2 夜间构建版本中,上面的示例 #2 将无法正常工作。当您尝试在 session_start() 之后重新启用严格模式时,您将收到以下错误
"ini_set(): 会话处于活动状态。您无法在此时间更改会话模块的 ini 设置"
我想这意味着对于任何您执行会话 ID 重新生成或会话 ID 转发(从执行了最近会话 ID 重新生成的会话到新会话 ID)的会话。您只需接受在该活动会话的剩余时间内禁用严格模式。
只要您遵循每个请求一个会话的设计(即您没有处理多个并发会话),我认为这并不构成安全问题。
licp - 不,session_regenerate_id() 不会销毁任何保存的会话数据。
elger,我更喜欢以下顺序
[code]
// 使用当前 session_id 填充 $_SESSION,其中包含任何先前保存的会话数据
session_start();
...
// 删除与当前 session_id 关联的任何保存的数据,$_SESSION 不会发生变化
session_destroy();
// 更改 session_id,$_SESSION 不会更改
session_regenerate_id();
...
// 在当前 session_id 下保存任何 $_SESSION 数据
session_close();
[/code]
<?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,顺序如此。
我希望如果顺序正确,应该不会有任何问题。
不要在 start_session() 之后调用 session_name 或 session_id,如果您要使用它们。
感谢其他人的注意。
请注意,在对启用 cookie 的会话调用 session_regenerate_id 时,会发送新的 cookie。
确保重新加载页面,否则您将收到“session_destroy(): Session object destruction failed”错误。以下是示例
错误的
<?php
session_start();
session_regenerate_id();
session_destroy();
?>
正确的
<?php
if (!$_GET['mode']){
session_start();
session_regenerate_id();
header('location: '.$_SERVER['REQUEST_URI'].'?mode=destroy');
} else {
session_start();
session_destroy();
}
?>
我注意到这一点,因为使用谷歌搜索之前提到的错误会导致各种错误报告,但不会导致解决方案。(当然,解决方案是阅读手册)
关于丢失会话并尝试使用 session_regenerate_id 修复它的说明
确保您没有尝试将 SimpleXML 对象推送到会话中。它在先转换为数组之前不会生效。:)
对于 php 5.1> 用户,可能值得访问 http://ilia.ws/archives/47-session_regenerate_id-Improvement.html
通过检查源代码,我不确定在运行 session_regenerate_id() 之后,原始会话数据是否不会被销毁(仍然保留在系统中),嗅探器仍然可以通过应用原始会话标识符来劫持。
此外,我发现如果使用了用户级会话存储处理程序,session_regenerate_id() 将不起作用。
此功能似乎创建了一个新的会话 ID,而不会清除旧的会话数据。这是安全验证中非常重要的功能
$usedns = TRUE; // 用于消除使用 IP 链的代理导致的故障,但速度较慢
$useragent = getenv("HTTP_USER_AGENT");
$host = getenv("REMOTE_ADDR");
$dns = $global['dns'] ? @gethostbyaddr($host):$host;
session_start();
if(session_is_registered('securitycheck')) {
if(
(($_SESSION['session']['host'] != $this->host) && !$usedns)
|| ($_SESSION['session']['dns'] != $this->dns)
|| ($_SESSION['session']['useragent'] != $this->useragent)
) {
session_regenerate_id();
session_unset();
}
} else {
$currentdata = array();
$currentdata['host'] = $this->host;
$currentdata['dns'] = $this->dns;
$currentdata['useragent'] = $this->useragent;
session_register('securitycheck', $currentdata);
}
如果有人窃取了活动的 SID(例如,通过引用者或注入攻击),他将无法通过主机/DNS 或用户代理进行验证,并将获得一个新的(空的)SID,而不会中断原始会话。
如有任何意见,请发邮件给我:php at 5mm de
要“启动新会话”,请尝试以下操作
<?php
session_start();
session_regenerate_id();
session_destroy();
unset($_SESSION);
session_start();
?>