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的特殊实现机制
- 算法差异化密钥处理:如上述所说,RS系列算法支持传入文件路径自动读取,HS系列算法直接使用字符串作为密钥。
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'));
攻击步骤
- 攻击者发现服务端会根据
kid读取文件内容作为密钥,且未限制文件路径范围。 - 攻击者选择一个已知内容的文件(比如系统中存在的
/etc/hostname,假设内容为test-server)。 - 攻击者构造payload(比如将
is_admin设为1),使用test-server作为密钥,调用JWT::encode生成Token,并将kid设为/etc/hostname。 - 攻击者将生成的Token发送给服务端,服务端会读取
/etc/hostname的内容作为密钥验证签名,从而通过验证,让攻击者获得管理员权限。
漏洞防护要点
- 对
kid字段做严格的白名单校验,仅允许预定义的合法密钥ID。 - 禁止直接使用
kid作为文件路径读取密钥,避免路径遍历攻击。 - 密钥存储应与业务代码隔离,避免通过文件读取方式加载密钥(建议直接将密钥配置在环境变量中)。
内容的提问来源于stack exchange,提问作者Johnny
相关产品推荐
相关产品推荐

