PAYATHON 2026

在 $_SESSION 变量中保存敏感信息是否安全?

支付老李

结论

不要把信用卡号、登录密码、CVV/CVC、银行卡密码等高度敏感的信息保存到 $_SESSION。

PHP 的 Session 通常把数据保存在服务器端,浏览器只保存 Session ID。与直接把敏感信息写入 Cookie、URL 或隐藏表单相比,这种方式更安全,但 Session 并不是保险箱。数据可能以可读形式存放在磁盘、数据库或 Redis 中,也可能通过会话劫持、服务器入侵、调试输出、日志记录或错误配置泄露。

更合适的处理方式是:

  • Session 只保存用户 ID、购物车 ID、订单 ID和权限标记等必要状态。
  • 密码只在数据库中保存由专用算法生成的哈希,用户登录后不要再把密码写入 Session。
  • 信用卡信息交给合规的支付服务商处理,应用只保存支付令牌、支付方式 ID 或掩码信息。
  • 不得保存 CVV/CVC 和银行卡密码,完成授权后也不应继续保留。
  • 全程使用 HTTPS。TLS 必不可少,但无法解决服务器端的存储风险。

$_SESSION 实际保存在哪里

在常见的 PHP 配置中:

$_SESSION['user_id'] = 123;

浏览器收到的通常只是类似下面的 Cookie:

PHPSESSID=a1b2c3d4...

真正的 Session 内容保存在服务器端,具体位置由 session.save_handler 决定,可能包括:

  • 本地 Session 文件;
  • Redis 或 Memcached;
  • 数据库;
  • 自定义 Session 存储;
  • 云平台提供的共享会话服务。

因此,即使攻击者看到了 Cookie,通常也无法直接从中读取信用卡号。但如果攻击者取得了有效的 Session ID,就可能冒充用户访问服务器,间接使用 Session 中的信息或执行支付操作。

Session 数据也不一定经过加密。默认文件存储、数据库记录或 Redis 数据都可能以可读或容易解析的形式存在。

a) 连接不安全时的风险

如果网站仍在使用普通 HTTP,风险很高。

攻击者可能通过监听网络流量取得:

  • 用户输入的账号和密码;
  • 信用卡号及其他支付信息;
  • Session Cookie;
  • 页面返回的个人信息;
  • 支付流程中的订单和身份数据。

即使信用卡号只保存在服务器端的 Session 中,攻击者截获 Session ID 后仍可能劫持用户会话。如果应用允许已登录用户直接发起支付,攻击者甚至不需要知道信用卡号。

所以,不安全的连接不应承载登录、支付或其他涉及个人数据的操作。仅仅把敏感信息从页面移到 $_SESSION,并不能防止传输链路遭到窃听。

b) 使用 SSL/TLS 时的风险

现在通常应使用现代 TLS,也就是 HTTPS,而不是旧式 SSL。TLS 能保护浏览器与服务器之间的数据传输,减少流量被窃听或篡改的风险,也能防止 Session Cookie 在传输途中被直接截获。

但 HTTPS 只能保护传输过程,无法保护服务器内部的数据。以下风险依然存在:

  • Web 服务器或 Session 存储遭到入侵;
  • Redis、数据库或 Session 文件的权限配置错误;
  • Session 数据进入备份、快照或故障转储;
  • 开发人员通过 var_dump($_SESSION) 输出敏感信息;
  • 错误追踪、性能监控或日志系统记录 Session;
  • 应用存在任意文件读取、代码执行或反序列化漏洞;
  • XSS 窃取用户的操作能力或读取页面中的敏感数据;
  • Session 固定、Session 劫持或会话未及时失效;
  • 内部人员获得超出职责范围的数据访问权限。

因此,HTTPS 不会让“把信用卡号和密码放进 Session”成为合理的设计。它只是整个安全体系中不可缺少的一层。

密码不应保存到 Session

应用不应该能够从数据库中读取用户的明文密码。如果数据库保存的是可以还原的密码,设计本身就存在严重问题。

密码应使用 password_hash() 生成不可逆哈希:

<?php

$passwordHash = password_hash($password, PASSWORD_DEFAULT);

// 将 $passwordHash 写入数据库

登录时使用 password_verify() 验证:

<?php

if (password_verify($submittedPassword, $storedPasswordHash)) {
    session_regenerate_id(true);
    $_SESSION['user_id'] = $userId;
    $_SESSION['authenticated'] = true;
}

验证完成后,无需把明文密码或密码哈希保存到 Session。Session 只需记录哪个用户已经通过身份验证。

密码哈希也不能作为后续操作的身份凭据。它只应用于密码验证,并保存在受到专门保护的身份数据库中。

信用卡信息应如何处理

应优先使用支付服务商提供的托管支付页面、支付组件或令牌化接口。比较理想的流程是:

  1. 浏览器把卡片信息直接提交给支付服务商。
  2. 支付服务商返回支付令牌或支付方式 ID。
  3. 应用保存该令牌,不保存完整卡号。
  4. 后续支付通过令牌调用支付接口。
  5. 页面只显示卡片品牌、末四位等非完整信息。

例如,Session 可以保存:

<?php

$_SESSION['checkout'] = [
    'cart_id' => 'cart_8f31',
    'order_id' => 'order_2048',
    'payment_method_id' => 'pm_token_from_provider',
];

不要这样保存:

<?php

$_SESSION['credit_card_number'] = '4111111111111111';
$_SESSION['cvv'] = '123';
$_SESSION['password'] = 'plain-text-password';

即使只保存几分钟,这些数据仍可能被 Session 后端、备份、日志或调试工具捕获。某类支付数据能否保存,还要看适用的 PCI DSS 要求、支付机构规则和当地法律,不能因为“Session 会自动过期”就认定符合规定。

避免 SQL 查询不是保存敏感信息的理由

一次经过索引优化的数据库查询,通常比扩大敏感数据的暴露范围更容易控制。性能问题应该通过缓存非敏感数据、优化查询和合理设计支付流程来解决。

Session 中可以保存:

  • user_id;
  • cart_id;
  • order_id;
  • 是否已经登录;
  • 必要的权限或流程状态;
  • 短期有效的支付令牌引用。

数据库或支付服务中保留:

  • 用户资料的权威版本;
  • 密码哈希;
  • 支付令牌与用户的绑定关系;
  • 订单及支付状态。

需要读取用户信息时,应通过用户 ID 查询。不要只为减少一次 SQL 查询,就在 Session 中复制整份敏感资料。

PHP Session 的基本安全配置

下面是基础配置示例,具体参数还要根据部署环境调整:

<?php

ini_set('session.use_only_cookies', '1');
ini_set('session.use_strict_mode', '1');
ini_set('session.cookie_httponly', '1');
ini_set('session.cookie_secure', '1');
ini_set('session.cookie_samesite', 'Lax');

session_start();

也可以在启动时明确设置 Cookie 参数:

<?php

session_set_cookie_params([
    'lifetime' => 0,
    'path' => '/',
    'domain' => '',
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Lax',
]);

session_start();

登录成功后应更新 Session ID:

session_regenerate_id(true);

用户退出登录时,应清理服务器端 Session,并使 Cookie 失效:

<?php

session_start();
$_SESSION = [];

if (ini_get('session.use_cookies')) {
    $params = session_get_cookie_params();

    setcookie(session_name(), '', [
        'expires' => time() - 42000,
        'path' => $params['path'],
        'domain' => $params['domain'],
        'secure' => $params['secure'],
        'httponly' => $params['httponly'],
        'samesite' => $params['samesite'] ?? 'Lax',
    ]);
}

session_destroy();

还应设置合理的空闲超时和最长会话时间。修改支付方式、提交付款等高风险操作需要重新验证身份,仅依赖 Session 中的 authenticated = true 往往不足以保护这类交易。

还需要注意什么

HttpOnly 能减少 JavaScript 直接读取 Session Cookie 的机会,但无法消除 XSS。攻击脚本仍可能利用用户现有的会话发起操作。

SameSite 可以减少部分 CSRF 风险,但不能取代 CSRF Token。付款、修改地址、绑定支付方式等请求仍需要明确的 CSRF 防护。

Session 存储本身也要妥善保护:

  • Session 文件不能放在可由 Web 直接访问的目录中;
  • Redis、数据库等存储应限制网络访问并启用身份验证;
  • 日志、错误页面和监控数据不得记录 Session 内容;
  • 备份和快照应加密并限制访问;
  • 不要在生产环境输出 $_SESSION;
  • Session 到期后,要确保服务器端记录同步失效;
  • 高敏感数据应遵循“没有必要就不收集、不复制、不保存”的原则。

判断时不该只问“$_SESSION 能不能保存”,而应该问“应用是否真的需要持有这份数据”。对于密码、完整信用卡号和 CVV/CVC,答案通常是否定的。Session 适合保存短期业务状态,以及无法单独使用的标识符,不适合充当敏感凭据仓库。

备注:内容仅供参考。