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转义导致验证失败
这是少数用户异常的直接诱因,逻辑链如下:
- 用户输入含特殊字符(如
<、>、&)的密码,access.php先执行htmlspecialchars($password)转义,再生成MD5哈希与数据库匹配,第一次验证成功。 - 登录页面将转义后的密码存入
$_SESSION['password']。 - 用户访问内部页面时,
access.php从Session取出已转义的密码,再次执行htmlspecialchars()做二次转义(例如原密码<test>第一次转义为<test>,二次转义后变成&lt;test&gt;)。 - 二次转义后的密码生成的MD5哈希与数据库中原密码的哈希完全不符,验证失败,用户被踢回登录页。
- 重置密码后,用户设置的密码无特殊字符,转义前后内容一致,二次转义不改变哈希值,验证恢复正常。
其他潜在问题与排查点
- Session存密码的高危风险:将密码(哪怕转义过)存在Session中极易泄露用户敏感信息,正确做法是存储用户ID或随机登录令牌,而非密码。
- SQL注入隐患:
access.php中的更新、查询语句使用字符串拼接(如update users set sid='$sid'...),存在注入风险,必须改用预处理语句绑定参数。 - Session ID存储逻辑冗余:将
session_id()存入用户表毫无必要——PHP的Session机制本身已维护会话有效性,且同一用户多设备登录时会覆盖sid,导致旧设备登录失效。 - 重复验证的性能浪费:每个受保护页面都执行完整的数据库查询和更新,增加数据库负载,应优化为登录时验证一次,后续通过Session中的用户ID快速获取信息。
修复建议
- 移除Session存储密码的逻辑:登录验证通过后,仅存入用户ID、用户名等非敏感信息,例如
$_SESSION['user_id'] = $user['id'];。 - 修改验证逻辑:受保护页面通过Session中的用户ID查询数据库,无需重复验证密码;若需加强安全,可生成随机登录令牌并存入Session和用户表,通过令牌验证身份。
- 取消密码的不必要转义:密码仅需在存入数据库前做哈希处理,
htmlspecialchars是用于输出到HTML页面防止XSS的,不需要用于密码验证流程。 - 替换所有字符串拼接的SQL语句:改用预处理语句绑定参数,彻底消除SQL注入风险。
内容的提问来源于stack exchange,提问作者Tim
相关产品推荐
相关产品推荐

