You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP基于Session的登录部分用户跳转受保护页面时被登出问题

问题描述

网站采用Session机制保护内容:用户输入用户名密码,验证通过后授予访问权限,否则显示错误。系统99.9%正常,但数千用户中有约15位出现异常:登录成功进入账户门户后,点击任意内部受保护页面链接会被重定向回登录界面;再次登录后问题依旧。重置用户密码后,问题消失,怀疑与密码内容有关(已排除注入问题)。

相关代码片段

登录页面代码

session_start();

include 'access.php';

$_SESSION['username'] = $username;
$_SESSION['password'] = $password;  

// redirect to account home page on successful login
header('Location: ../account');
exit; // redirect to account home page

access.php(登录验证)代码

require_once "../common/db.php"; // dbcon

$year = DATE("Y");

session_start();
$sid = session_id();

@$username = isset($_POST['username']) ? $_POST['username'] : $_SESSION['username'];
@$password = isset($_POST['password']) ? $_POST['password'] : $_SESSION['password'];

if(!isset($username)) { // SHOW LOGIN FORM
    exit;
} // end if

$username = htmlspecialchars($username);
$password = htmlspecialchars($password);
$hashed = md5($password);

$sql = "SELECT * FROM users WHERE username=? and password=? AND status='1'"; // SQL with parameters
$stmt = $connection_mysql->prepare($sql); 
$stmt->bind_param("ss", $username,$hashed);
$stmt->execute();
$result = $stmt->get_result(); // get the mysqli result
$user = $result->fetch_assoc(); // fetch data   

if (mysqli_num_rows($result) == 0) {
    unset($_SESSION['username']);
    unset($_SESSION['password']); /// SHOW LOGIN ERROR MESSAGE 
    exit;

} // end if

if (mysqli_num_rows($result) !==0)  {
    
    // update members to add sid for current session
    $datetime = DATE("Y-m-d H:i:s");
    $update = "update users set sid='$sid', last_login='$datetime' where username='$user[username]' and password='$user[password]'";
    $update_result = mysqli_query($connection_mysql, $update);
    
    
    // get user info
    $sql = "SELECT * FROM users where username='$user[username]' and password='$user[password]' and sid='$sid' AND status='1'";
    $result = mysqli_query($connection_mysql,$sql);
    $user = $result->fetch_assoc(); // fetch data
    
    
}
问题分析与排查方向

核心原因:密码二次HTML转义导致验证失败

这是少数用户异常的直接诱因,逻辑链如下:

  1. 用户输入含特殊字符(如<、>、&)的密码,access.php先执行htmlspecialchars($password)转义,再生成MD5哈希与数据库匹配,第一次验证成功。
  2. 登录页面将转义后的密码存入$_SESSION['password']。
  3. 用户访问内部页面时,access.php从Session取出已转义的密码,再次执行htmlspecialchars()做二次转义(例如原密码<test>第一次转义为&lt;test&gt;,二次转义后变成&amp;lt;test&amp;gt;)。
  4. 二次转义后的密码生成的MD5哈希与数据库中原密码的哈希完全不符,验证失败,用户被踢回登录页。
  5. 重置密码后,用户设置的密码无特殊字符,转义前后内容一致,二次转义不改变哈希值,验证恢复正常。

其他潜在问题与排查点

  • Session存密码的高危风险:将密码(哪怕转义过)存在Session中极易泄露用户敏感信息,正确做法是存储用户ID或随机登录令牌,而非密码。
  • SQL注入隐患:access.php中的更新、查询语句使用字符串拼接(如update users set sid='$sid'...),存在注入风险,必须改用预处理语句绑定参数。
  • Session ID存储逻辑冗余:将session_id()存入用户表毫无必要——PHP的Session机制本身已维护会话有效性,且同一用户多设备登录时会覆盖sid,导致旧设备登录失效。
  • 重复验证的性能浪费:每个受保护页面都执行完整的数据库查询和更新,增加数据库负载,应优化为登录时验证一次,后续通过Session中的用户ID快速获取信息。

修复建议

  1. 移除Session存储密码的逻辑:登录验证通过后,仅存入用户ID、用户名等非敏感信息,例如$_SESSION['user_id'] = $user['id'];。
  2. 修改验证逻辑:受保护页面通过Session中的用户ID查询数据库,无需重复验证密码;若需加强安全,可生成随机登录令牌并存入Session和用户表,通过令牌验证身份。
  3. 取消密码的不必要转义:密码仅需在存入数据库前做哈希处理,htmlspecialchars是用于输出到HTML页面防止XSS的,不需要用于密码验证流程。
  4. 替换所有字符串拼接的SQL语句:改用预处理语句绑定参数,彻底消除SQL注入风险。

内容的提问来源于stack exchange,提问作者Tim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 19:14:52