JWT(JSON Web Token)是一个开放标准(RFC7519),它定义了一种在双方之间的JSON对象内提供信息的方法。本标准旨在帮助安全地传输信息,但任何标准或技术在使用不当时都不会保护您。
为了确定在Node.js中使用JWT时可能出错的地方,我对使用最流行的JWT库的NPM模块进行了安全检查。使用静态分析工具,我检查了2000个NPM模块的安全弱点和漏洞。这篇文章总结了我在研究过程中发现的一些常见错误,包括:
除了描述这些问题以便您可以避免它们之外,这篇文章还包括开放源码规则,使您可以更容易地手动审核代码库以检测它们,或者将这些漏洞包含在CI中,这样这些漏洞从一开始就不会合并到您的代码中。
最基本的错误是将硬编码的秘密用于JWT生成/验证。这使得攻击者能够在源代码(以及其中的JWT秘密)公开暴露或泄露的情况下伪造令牌。
这不仅引入了漏洞,还被认为是软件反模式。您应该将JWT机密与代码分开,例如,在单独的配置文件或环境变量中。
const jwt=REQUIRED(";jsonwebToken";);const Secure=";硬编码-SECRET-HERE";;//😈类{静态签名(Obj){返回jwt.。sign(obj,ret,{});}}。
值得一提的是,使用JWT的一种流行方式是在其他库中使用,例如通过Passport,这是一种流行的Node.js身份验证中间件。
var JwtStrategy=Required(';Passport-Jwt&39;).Strategy,ExtractJwt=Required(';Passport-Jwt&39;).ExtractJwt;var opts={SecretOrKey:';Hard code-Secure-Here;//😈}Passport。use(new(opts,function(JWT_PARYLOAD,DONE){//code}));
尽管这是一个已知且相当明显的问题,但在开发时使用硬编码的秘密,然后意外地将其留在代码库中的情况仍然很常见。幸运的是,使用SAST工具查找硬编码密码也非常容易,特别是使用Semgrep,它可以帮助查找规则非常简单的复杂代码模式。用于检测硬编码密码的规则:
几年前,允许令牌具有无算法是一个严重的漏洞。现在,大多数流行的JWT库在没有显式启用None算法的情况下都不允许使用None算法解码或验证令牌。与硬编码的秘密一样,在测试或调试后很容易在代码库中留下‘';NONE';`。
让JWT=REQUIRED(";jsonwebToken";);let Secure=";Some-Secret";;JWT。验证(";Token-here";,Secret,{算法:[";RS256";,";None";]});//😈';None';Allowed。
无论如何,如果您在处理代码后忘记删除它,使用Semgrep.Rules检测代码中允许的“None”算法也很容易捕捉到它:
有时开发人员依赖自己的令牌验证方法,而不是使用内置的API,或者完全省略验证,难怪这通常会给攻击者带来伪造令牌内信息的机会。
const JWT=Required(";jsonwebToken";);const checkToken=(Token,fresh hToken,Key)=>;{if(JWT.。Verify(Refresh Token,Key)){//😈只有`renhToken`验证返回jwt。DECODE(TOKEN).param=JWT。decode(Fresh HToken).param;}返回FALSE;};
注意:在上面的示例中只验证了fresh hToken,这给攻击者提供了操纵函数结果的机会。通过更改存储在令牌中的参数属性值,攻击者可以强制checkToken函数的结果为TRUE并通过验证。
最重要的是,在验证令牌(发布日期、ID等)之前,从令牌获取某些数据是非常典型的。然后将其用作验证上下文。通常情况下,它是无害的,但前提是这些数据不会进一步深入。如果未经验证的令牌中的信息被传递到代码的其他部分,则可能会引入漏洞。
//令牌验证逻辑const jwt=request(";jsonwebToken";);函数checkToken(Token){const颁发者=jwt。DECODE(TOKEN).Issuer;IF(findIssuer(Issuer)&;&;JWT.。验证(标记,密钥){//此处的代码}否则{抛出新的(";不是有效的标记";);}}//来自不同模块函数findIssuer(ISS)的数据库实用程序{//.。数据库。查找(ISS);}。
(在上面的示例中,未经验证的颁发者值在验证令牌(https://owasp.org/www-project-top-ten/OWASP_Top_Ten_2017/Top_10-2017_A1-Injection).)之前传递给另一个函数。如果不小心使用,它可能会导致不同类型的注入漏洞,特别是当它位于代码库的不同部分时。)。
另外,不要忘记,即使令牌经过正确验证,其中存储的数据也应该被视为用户输入,并根据上下文进行验证和清理。
在将对象转换为JWT令牌时,如果没有显式地将其拆分成几个部分,很容易失去对对象内部内容的控制,从而泄露一些敏感信息。
在使用Mongoose、Sequelize等ORM库时,这是一个非常普遍的错误。ORM模型在创建时不包含任何敏感数据,但是当情况发生变化时,很容易忘记ORM对象也会传递给JWT令牌。
//Mongoose model const mongoose=Required(';mongoose';),Schema=mongoose.Schema;const schema=new({name:string,password:string,admin:boolean});const user=mongoose。model(';LocalUser';,schema);//Express控制器路由器。POST(';/登录';,(请求,资源)=>;{用户。findOne({name:req.body.name},function(err,user){var Token=JWT.。Sign(User,Key,{expiresIn:60*60*10});//😈将User对象直接传递给JWT res。json({SUCCESS:TRUE,Message:';享受您的Token!';,Token:Token});});}。
这些是开发人员在Node.js项目中使用JWT时最常犯的错误。确保安全,不要忘记在代码库中自动执行安全扫描。