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

PHP Firebase JWT跨平台验证失败及漏洞验证技术咨询

PHP Firebase JWT签名验证失败原因与kid参数漏洞分析

问题场景

我在PHP应用中使用PHP Firebase JWT实现JWT功能,代码如下:

$payload = [
    'iss' => 'http://test.com',  
    'aud' => 'http://test.com',  
    'iat' => time(),  // Issued at
    'exp' => time() + 3600,  // Expiration (1 hour)
    'data' => [
        'user_id' => $user['id'],
        'email' => $user['email'],
        'role' => $user['role'],
        'is_admin' => $user['isadmin']
    ]
];
$jwt = JWT::encode($payload, $key, 'HS256', $key_file);

生成的Token为:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6Ii92YXIvd3d3L215a2V5LmtleSJ9.eyJpc3MiOiJodHRwOi8vdGVzdC5jb20iLCJhdWQiOiJodHRwOi8vdGVzdC5jb20iLCJpYXQiOjE3MjM5NzQ5OTMsImV4cCI6MTcyMzk3ODU5MywiZGF0YSI6eyJ1c2VyX2lkIjoxLCJlbWFpbCI6ImFiYyIsInJvbGUiOiJ1c2VyIiwiaXNfYWRtaW4iOjB9fQ.uC13pivSZGnPr6i8zmPCfEFMsykWR5miIK8t0-DCnug

密钥文件内容为hellohellohellohellohellohellohe。使用在线JWT验证工具时无法匹配签名,同时希望演示普通用户通过修改kid参数生成可被站点接受的Token的漏洞。

签名验证失败的原因

核心问题:HS256算法下密钥参数的处理逻辑

PHP Firebase JWT对不同签名算法的密钥参数处理存在差异:

  • 对于RS系列算法(如RS256),JWT::encode的第二个参数可以是密钥文件路径,库会自动读取文件内容作为密钥。
  • 对于HS系列算法(如HS256),库不会解析路径,会直接将第二个参数的字符串当作签名密钥使用。

你的代码中使用了HS256算法,如果$key变量存储的是密钥文件路径(而非文件内容hellohellohellohellohellohellohe),那么签名时实际使用的密钥是路径字符串(比如/var/www/mykey.key),而非文件里的内容。而你在在线工具中输入的是密钥文件内容,自然无法匹配签名。

验证方法修正

如果要让在线工具验证通过,需要确保:

  • 代码中$key变量的值是密钥文件的内容(即hellohellohellohellohellohellohe),而非文件路径。
  • 在线工具中输入的密钥与代码中$key的值完全一致,无需关注kid字段(在线工具验证HS256签名时不需要kid,仅需密钥本身)。

PHP Firebase JWT的特殊实现机制

  1. 算法差异化密钥处理:如上述所说,RS系列算法支持传入文件路径自动读取,HS系列算法直接使用字符串作为密钥。
  2. kid参数的作用:JWT::encode的第四个参数是kid(密钥ID),仅会被写入JWT头部,库本身不会用它去加载密钥——密钥的加载逻辑完全由开发者在验证阶段实现。

kid参数漏洞演示

漏洞的核心在于服务端验证时的密钥加载逻辑,如果服务端代码存在以下情况,攻击者即可通过修改kid生成合法Token:

漏洞场景示例(服务端验证代码)

假设服务端验证时,根据kid字段的值读取对应文件作为密钥:

$token = $_SERVER['HTTP_AUTHORIZATION'];
$decoded_header = JWT::decodeHeader($token);
$kid = $decoded_header->kid;
// 直接使用kid作为文件路径读取密钥,未做过滤
$key = file_get_contents($kid);
$decoded = JWT::decode($token, new Key($key, 'HS256'));

攻击步骤

  1. 攻击者发现服务端会根据kid读取文件内容作为密钥,且未限制文件路径范围。
  2. 攻击者选择一个已知内容的文件(比如系统中存在的/etc/hostname,假设内容为test-server)。
  3. 攻击者构造payload(比如将is_admin设为1),使用test-server作为密钥,调用JWT::encode生成Token,并将kid设为/etc/hostname。
  4. 攻击者将生成的Token发送给服务端,服务端会读取/etc/hostname的内容作为密钥验证签名,从而通过验证,让攻击者获得管理员权限。

漏洞防护要点

  • 对kid字段做严格的白名单校验,仅允许预定义的合法密钥ID。
  • 禁止直接使用kid作为文件路径读取密钥,避免路径遍历攻击。
  • 密钥存储应与业务代码隔离,避免通过文件读取方式加载密钥(建议直接将密钥配置在环境变量中)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 10:05:15