在PHP中从HTTP切换到HTTPS时会丢失会话

64
当用户进入结账页面时,网站将从http://sitename.com 切换到 https://sitename.com。这将导致$_SESSION变量丢失。该网站具有有效的SSL证书,这可能有些用处。

1
你使用的是哪个浏览器?是否涉及任何子域名,例如“www.”?您能否在其他地方重现此问题? - Unsigned
16个回答

71
当您在同一服务器上的HTTP和HTTPS服务之间切换时,您的HTTP会话ID不会传递到HTTPS会话中。您可以通过三种可能的方式之一,在HTTP页面中将会话ID传递到HTTPS页面来设置它:
PHP:session_start
session_start()基于通过请求(例如GET、POST或cookie)传递的当前会话ID创建会话或恢复当前会话。
当您使用会话时,通常会使用session_start()启动脚本。如果浏览器设置了会话ID cookie,则session_start()将使用该会话ID。如果浏览器没有设置会话ID cookie,则session_start()将创建一个新的会话ID。
如果未设置会话ID(在您的示例中,浏览器为HTTPS会话创建新的会话ID cookie),则可以使用session_id()函数设置它。session_id()还方便地返回会话ID作为字符串。
...

$currentSessionID = session_id();

...

$currentSessionID变量设置为当前会话ID,并且

...

session_id($aSessionID);

...

将浏览器中的sessionID cookie设置为$aSessionID。来自PHP: session_id

以下是两个脚本的示例。一个通过HTTP访问,另一个通过HTTPS访问。它们必须在同一服务器上以维护会话数据。

脚本1(HTTP):

<?php

// This script will create a session and display a link to your secure server address
// to transfer your session ID. In this example, the secure page to receive the session
// ID is located at http://www.yoursite.com/safePages/securePage.php

// Start a session using the current session ID stored in a cookie, or create
// a new session if none is set.
session_start();

$currentSessionID = session_id();

// Set a variable that will be retrieved with the HTTPS script.
$_SESSION['testvariable'] = 'It worked';

// $secureServerDomain is the domain of your secure server
$secureServerDomain = 'www.yoursite.com';

// $securePagePath is the path to the page that will receive and set the session ID.
$securePagePath = '/safePages/securePage.php'

echo '<a href="https://' . $secureServerDomain . $securePagePath . '?session="' . $currentSessionID . '">Click here to transfer your session to the secure server</a>';

?>

脚本2(HTTPS)

<?php

// Retrieve the session ID as passed via the GET method.
$currentSessionID = $_GET['session'];

// Set a cookie for the session ID.
session_id($currentSessionID);

// Start a session.
session_start();

// Test retrieval of variable set when using HTTP.
if (!empty($_SESSION['testvariable'])) {
      echo $_SESSION['testvariable'];
} else {
      echo 'It did not work.';
}

?>

为了使此方法起作用,HTTP和HTTPS服务器必须使用相同的会话数据存储基质(即对于默认文件处理程序,在同一台物理机器上运行,使用相同的php.ini)。这里存在一些安全漏洞,因此我不会使用此代码来传输敏感信息。它只是作为一个可行的示例。

当我以前遇到这个问题时,我想出了上面的快速修复方法,但我刚刚想起了问题的根本原因。我从http://www.example.com/page.php转到https://example.com/page.php(注意缺少“www”)。确保http://www.example.com/page.php将链接到https://www.example.com/page.phphttp://example.com将链接到https://example.com/page.php

附注:我实际上没有运行这些脚本,因此可能会有一两个拼写错误,导致它们无法正常运行。


2
使用一个带盐哈希将确保此过程的安全性。 - Gumbo
我看不出这里安全问题的本质 - 你知道任何描述它们的资源吗? - bcoughlan
9
安全问题:有人可能会持续攻击您的网站并猜测session_id变量,最终劫持其他人的会话。我不明白如何使用加盐哈希,因为那是一个单向操作;您可以对密码进行加盐哈希,并为密码尝试做同样的操作,但在这种情况下,那不会完全起作用;您需要原始值。 - Dean J
2
@DeanJ - 你可以在服务器端生成一个校验和,并将其解析为URL参数。例如,你可以像这样生成哈希值:$hash = md5($currentSessionID.'somesecretsalt'),然后URL应该看起来像 ?session='.$currentSessionID.'&hash='.$hash。在目标站点上,你可以检查 md5($_GET['session'].'somesecretsalt') == $_GET['hash'] 是否成立。这样你就可以知道,会话ID不是随意制造的。会话ID必须与服务器生成的哈希值匹配(当使用长盐值时,几乎不可能破解)。 - Trolley
在注意到问题“从http://www.example.com/page.php转到https://example.com/page.php”后,给出+1 - 这对我也是一个问题。我通过修改我的.htaccess文件来完全阻止www来解决它:RewriteCond %{HTTP_HOST} ^www.example.com$ [NC] RewriteRule ^.*$ http://example.com%{REQUEST_URI} [R=301,L] - JSP64
你能否使用相同的技术来处理不同端口的两个网站,例如 mywebsite.com 和 mywebsite.com:80/index.html? - StackOverQuestions

16
听起来会话cookie被设置为安全模式。Cookie有一个"secure"标志,如果设置为true,则表示该cookie不会发送到非https站点。PHP可能正在使用它的会话cookie。您可以使用session_set_cookie_params函数或php.ini中的session.cookie_secure设置更改此设置。

OP指定了在转移到加密连接时cookie丢失。此处不适用secure标志。 - Unsigned
1
@Unsigned - 如果您切换到安全页面并告诉页面仅使用安全会话cookie,则必须创建新的安全cookie,不能使用现有的不安全cookie。 - martinstoeckli
@martinstoeckli - 确实如此。但默认设置是“关闭”的。如果它是“开启”的,那么cookie就不会被发送到HTTP页面,这意味着该cookie也不应该存在于那里。因此,该cookie不安全。 - Unsigned
@JW是正确的,我花了一些时间才明白,'session_start(); setcookie(session_name(), session_id(), NULL, NULL, NULL, 0);'会为http和https设置一个cookie,防止丢失会话。 - ontananza

12

我也需要禁用suhosin.cookie.cryptdocroot - mrwalker

6
以下解决方案假定安全和非安全服务器可以访问相同的后端服务(缓存、数据库存储等)。
当我们将用户完成购物后发送到结账流程时,我们不得不处理这个问题。为了解决这个问题,我们建立了一个缓存层,并缓存了所有相关数据。例如,我们从会话值中获取产品 ID 和用户 ID,对它们进行序列化,创建哈希,最后使用哈希作为键将会话数据存储在缓存中。然后我们将用户重定向到带有哈希的安全站点的网址。
当用户到达安全站点时,我们将尝试基于哈希从缓存中获取数据。然后,通过用户 ID 和产品 ID,我们可以从数据库中加载所有的价格和描述数据,并呈现给用户进行最终结账审核。
存在着缓存数据易失性的风险,但由于重定向发生得很快,我们从未遇到任何问题。

我们的解决方案很相似,只有一个区别。我建议将数据存储在数据库中,而你则暗示使用缓存。他如何创建一个公共内存以在不同会话之间使用? - Uğur Gümüşhan
@UğurGümüşhan:不错。相对于流量而言,数据库解决方案会更慢,但是它是持久的。正如我在帖子中提到的那样,我们使用在发送端(非SSL)生成的哈希来查找接收端(SSL)上的数据。 - Mike Purcell
这与在URL中发送会话ID(如其他答案中所提到的)有何不同?除了哈希可能更长且更难猜测之外? - MrWhite
1
@w3d:不确定你的意思。如果您愿意,可以使用会话ID或任何字符组合。关键是,在一侧缓存会话数据,并使用查找键将用户重定向到另一侧,以便您可以使用旧会话的数据创建新会话。 - Mike Purcell

1

你不能在不同的域之间传递会话值。你必须使用 http post-get 或数据库来传递你的值。为了安全起见,你可以将所有的值连接成一个字符串并使用。

sha1($string)

将其与您的值一起发布并为其他页面获取的值计算sha1,然后比较哈希值。

不要在不同域上使用post方法,否则浏览器会显示安全消息。

使用url进行get方法不安全,您需要在重定向页面上请求密码以允许系统中的get参数。

如果需要安全性,请勿使用cookies。

我建议的方法是,将值保存在数据库中并生成一个密钥,然后使用您的密钥创建重定向链接,使用具有密钥的get参数转发用户页面,然后被重定向到的页面获取该密钥,提取数据并删除密钥。您可以使用sha1生成密钥。

PAGE 1---
$key=sha1($allvalsconcat);
//insert your session values to a database also the key in a column
header("Location: page2.php?key=".$key);

PAGE 2---
// select from database where key=$_GET["key"];
// delete from table where key=$key

这是相当安全的。

可能发生的事情: 脚本输入随机值到参数“key”中,从而加载数据到您的内存中?

这不会发生,因为在使用后您会删除该条目。 一些常见的误解是获取值是不安全的,应该始终避免。

如果想要完美的性能,可以将表引擎类型设置为mysql中的“memory”。


1
你可以在HTTP和HTTPS之间或HTTPS和HTTP之间管理会话:
  1. 使用GET方法在页面之间传输会话ID

  2. 通过POST方法提交会话ID

  3. 使用文件保存会话

  4. 使用Cookies进行会话管理

  5. 使用数据库保存会话

以下示例可用于使用GET方法传输:

文件:http.php ……………

<?php

session_start();

$sessionID = session_id();

$_SESSION['demo'] = ‘Demo session between HTTP HTTPS’;

echo ‘<a href=”https://www.svnlabs.com/https.php?session=’.$sessionID.’”>Demo session from HTTP to HTTPS</a>’;

?>

文件:https.php ……………

<?php

$sessionID = $_GET['session'];

session_id($sessionID);

session_start();

if (!empty($_SESSION['demo'])) {
echo $_SESSION['svnlabs'];
} else {
echo ‘Demo session failed’;
}

?>

IE7:此页面包含安全和非安全项目

您必须在页面上使用相对路径来引用所有静态资源,如css、js、图像、flash等,以避免IE消息中的安全和非安全项目...

IE消息 IE消息


2
在URL中传递会话ID将使您面临各种攻击。仅使用cookie传输会话ID的原因是,您可以强制发送的cookie仅加密。 - martinstoeckli

1

看起来您的会话cookie是使用安全标志创建的,但由于结帐页面的URL存在问题,因此会话cookie未被传递。

或者,您的会话cookie不安全 - 只是结帐页面的URL有足够的差异({{link1:http://mysite.com}}与{{link2:http://www.mysite.com}}),浏览器不会发送cookie。

如果您想了解更多关于从http切换到https和反之亦然的信息,请查看{{link3:我的有关选择性ssl的文章}} :-)


1

除了大多数人在这里提到的传输加密信息之外,我建议您将其视为通过第三方API传输敏感信息。您如何知道有人没有欺骗请求?根据您的设置的敏感程度,有许多协议可以真正确认请求的真实性。如果不小心,您会使帐户面临被攻击的风险。

即使它在同一台服务器上,也要考虑以下内容:

当有人跟随链接、表单操作等传递加密密钥时,有什么可以防止他们在到达您网站的安全版本之前嗅探它?如果我在公共WIFI点,那并不是太牵强。我可以假装是您的网站,将请求重定向到我的笔记本电脑,抓取令牌,并将访问者重定向回原来的位置。他们会认为这是一个故障,并且毫不知情。现在我可以登录为他们,可能使用他们的信用卡购买价值10,000美元的物品并将其运送到其他地方。您在此处采取的谨慎程度应与敏感程度相匹配。

此外,确保您过期令牌(仅限一次使用,在X秒后过期等等),但我还建议在两端上使用Post-Redirect-Get模式,即:

不要在页面上或非安全站点的形式中显示直接链接,而是显示一个链接,然后在后端重定向(并处理所有令牌/加密内容)。到达安全版本时,执行相同操作(不要在URL中留下"?token=asdfjalksfjla"参数;将其重定向)。

因此,正式的基于令牌的系统旨在解决这个问题,但为此实现OAuth可能有些过度设计。在执行之前花些时间规划潜在漏洞。仅仅因为很难猜测令牌并不意味着这是不可能的(或者可能会有碰撞等问题),因此请做好计划。

您可能还需要比PHP内置处理程序更复杂的会话管理系统。我不知道是否可以强制PHP在多个访问之间继续会话(切换协议时会这样处理)。


1

考虑为所有页面使用HTTPS,这是避免此问题的最简单方法,也将提高您网站的安全性。

如果对于您来说所有页面使用SSL不是一个选项,那么您可以使用以下方法:在HTTP和HTTPS页面之间切换并使用安全会话cookie。其背后的想法是,您保留会话cookie不安全(因此可用于HTTP和HTTPS页面),但有第二个安全cookie来处理身份验证。这是分离“维护会话”和“身份验证”两个关注点的好方法。


0

我已经通过这个方法得到了解决方案。请尝试一下。

$_SESSION['test'] = 'test';
session_regenerate_id(true);

header("Location: /");// the header must be sent before session close
session_write_close(); // here you could also use exit();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接