ThinkPHP项目会话固定攻击防护

wen PHP项目 4

本文目录导读:

ThinkPHP项目会话固定攻击防护

  1. 核心策略:登录成功后强制更换 Session ID(最重要)
  2. 配置层面:修改配置文件(全局加固)
  3. 高级防护:绑定用户特征(防session劫持)
  4. 设置会话生命周期与空闲超时
  5. 应对未登录访问(防止非登录态下的固定)
  6. 框架版本差异说明
  7. 总结防线(实战步骤清单)

ThinkPHP 框架本身在会话管理上提供了一定的安全机制,但默认配置并不保证完全免疫会话固定攻击,你需要在项目中主动配置和编码来强化防护。

会话固定攻击的核心是:攻击者先获取/创建一个有效的会话ID,然后诱骗受害者使用这个ID进行登录,从而窃取受害者的登录状态。

以下是针对 ThinkPHP 项目(以 ThinkPHP 6/8 为例,5.x 亦有类似配置)的全面防护方案

核心策略:登录成功后强制更换 Session ID(最重要)

这是防止会话固定的根本措施,ThinkPHP 提供了 Session::regenerate() 方法。必须在用户登录成功、赋予权限之前调用

操作位置: 登录控制器方法中。

<?php
declare(strict_types=1);
namespace app\controller;
use think\facade\Session;
use think\Request;
class Login
{
    public function doLogin(Request $request)
    {
        // 1. 验证用户名密码(伪代码)
        $user = $this->validateUser($request->param('username'), $request->param('password'));
        if ($user) {
            // !!!关键步骤 !!!
            // 必须在写入用户数据之前,销毁旧会话ID,生成新ID
            // 这会强制浏览器更新Cookie,旧的固定ID将失效
            Session::regenerate();
            // 2. 写入用户登录状态
            Session::set('user_id', $user['id']);
            Session::set('user_role', $user['role']);
            return json(['status' => 1, 'msg' => '登录成功']);
        }
        return json(['status' => 0, 'msg' => '登录失败']);
    }
}

注意: 若使用原生 PHP 会话,等价写法是 session_regenerate_id(true),但 TP 封装后的 Session::regenerate() 更统一。


配置层面:修改配置文件(全局加固)

config/session.php 配置文件中,通过调整参数增加攻击难度。

return [
    // 会话ID长度(增加复杂度,防止暴力猜测)
    'id_length' => 32, 
    // 会话ID字符集(建议包含多种字符)
    'id_salt'   => 'YourRandomStringHere!@#', // 增加ID的随机性
    // 会话过期时间(缩短有效窗口,建议30分钟)
    'expire'    => 1800, // 30分钟
    // Cookie 安全属性(重要)
    'cookie'    => [
        // 只允许通过HTTPS传输会话ID,无则留空,有则设为true
        'secure'   => env('app_debug', false) ? false : true, 
        // 禁止JavaScript读取Cookie(防止XSS盗取)
        'httponly' => true,
        // 设置SameSite属性(防止CSRF和部分会话固定变种)
        'samesite' => 'Lax', // 可选 'Strict' 或 'Lax'
    ],
    // 会话使用前缀(避免与其他应用冲突)
    'prefix' => 'tp_',
    // 严格模式:如果客户端传递了未初始化的会话ID,则将其丢弃
    'strict_sessions' => true, 
];

高级防护:绑定用户特征(防session劫持)

即使攻击者获取了会话ID,如果服务端校验了客户端的特征信息(如 User-Agent、IP),攻击者也无法直接使用。注意: 建议绑定User-Agent,不建议绑定IP(因为手机网络切换IP会导致用户掉线)。

<?php
declare(strict_types=1);
namespace app\middleware;
use think\facade\Session;
use think\Request;
class SessionBind
{
    public function handle($request, \Closure $next)
    {
        // 检查用户是否已登录
        if (Session::has('user_id')) {
            // 绑定User-Agent(浏览器指纹)
            $bindUa = Session::get('bind_ua');
            if ($bindUa !== null) {
                // 如果User-Agent变了,销毁会话,要求重新登录
                if ($bindUa !== md5($request->header('user-agent'))) {
                    Session::clear();
                    return redirect('/login')->with('error', '安全校验失败,请重新登录');
                }
            } else {
                // 首次登录时写入绑定信息(建议在登录成功时写入,而非此处)
                Session::set('bind_ua', md5($request->header('user-agent')));
            }
        }
        return $next($request);
    }
}

建议在 登录成功 时直接写入绑定信息:

// 登录成功后追加这段代码
Session::set('bind_ua', md5($request->header('user-agent')));

设置会话生命周期与空闲超时

即使攻击者拿到了ID,如果长时间未操作,会话应自动过期。

// 登录成功后
session([
    'expire' => 1800 // 30分钟无操作则过期
]);

应对未登录访问(防止非登录态下的固定)

如果用户未登录,尽量不要依赖 Session 中的用户数据,或者对未登录的 Session 也设置较短的过期时间。


框架版本差异说明

  • ThinkPHP 5.xSession::regenerate()think\facade\Session 中存在,配置位于 application/config.phpextra/session.php
  • ThinkPHP 6/8: 配置位于 config/session.php,中间件机制更完善(可参考上文中间件写法)。

总结防线(实战步骤清单)

  1. 登录处: 调用 Session::regenerate()立即写入用户信息。
  2. 退出处: 调用 Session::clear() 后,再次调用 Session::regenerate() 防止会话变异。
  3. Cookie设置: 确保 httponly 设为 true
  4. 强验证: 若业务涉及敏感操作,建议对绑定User-Agent进行校验。
  5. 日志监控: 检测同一Session在不同IP下频繁登录的异常行为并强制下线。

如果你正在用的是 PHP 8 和 ThinkPHP 8,建议开启框架自带的 Session 初始化中间件,确保所有配置项都被正确加载。

抱歉,评论功能暂时关闭!