Headline
CVE-2020-15084: Merge pull request from GHSA-6g6m-m6h5-w9gf · auth0/express-jwt@7ecab5f
In express-jwt (NPM package) up and including version 5.3.3, the algorithms entry to be specified in the configuration is not being enforced. When algorithms is not specified in the configuration, with the combination of jwks-rsa, it may lead to authorization bypass. You are affected by this vulnerability if all of the following conditions apply: - You are using express-jwt - You do not have algorithms configured in your express-jwt configuration. - You are using libraries such as jwks-rsa as the secret. You can fix this by specifying algorithms in the express-jwt configuration. See linked GHSA for example. This is also fixed in version 6.0.0.
@@ -17,16 +17,34 @@ describe('failure tests’, function () { } });
it('should throw if algorithms is not sent’, function() { try { expressjwt({ secret: ‘shhhh’ }); } catch(e) { assert.ok(e); assert.equal(e.message, ‘algorithms should be set’); } });
it('should throw if algorithms is not an array’, function() { try { expressjwt({ secret: 'shhhh’, algorithms: ‘foo’ }); } catch(e) { assert.ok(e); assert.equal(e.message, ‘algorithms must be an array’); } });
it('should throw if no authorization header and credentials are required’, function() { expressjwt({secret: 'shhhh’, credentialsRequired: true})(req, res, function(err) { expressjwt({secret: 'shhhh’, credentialsRequired: true, algorithms: [‘HS256’]})(req, res, function(err) { assert.ok(err); assert.equal(err.code, ‘credentials_required’); }); });
it('support unless skip’, function() { req.originalUrl = '/index.html’; expressjwt({secret: 'shhhh’}).unless({path: '/index.html’})(req, res, function(err) { expressjwt({secret: 'shhhh’, algorithms: [‘HS256’], algorithms: [‘HS256’]}).unless({path: '/index.html’})(req, res, function(err) { assert.ok(!err); }); }); @@ -37,15 +55,15 @@ describe('failure tests’, function () { corsReq.headers = { 'access-control-request-headers’: ‘sasa, sras, authorization’ }; expressjwt({secret: 'shhhh’})(corsReq, res, function(err) { expressjwt({secret: 'shhhh’, algorithms: [‘HS256’]})(corsReq, res, function(err) { assert.ok(!err); }); });
it('should throw if authorization header is malformed’, function() { req.headers = {}; req.headers.authorization = 'wrong’; expressjwt({secret: 'shhhh’})(req, res, function(err) { expressjwt({secret: 'shhhh’, algorithms: [‘HS256’]})(req, res, function(err) { assert.ok(err); assert.equal(err.code, ‘credentials_bad_format’); }); @@ -54,7 +72,7 @@ describe('failure tests’, function () { it('should throw if authorization header is not Bearer’, function() { req.headers = {}; req.headers.authorization = 'Basic foobar’; expressjwt({secret: 'shhhh’})(req, res, function(err) { expressjwt({secret: 'shhhh’, algorithms: [‘HS256’]})(req, res, function(err) { assert.ok(err); assert.equal(err.code, ‘credentials_bad_scheme’); }); @@ -63,15 +81,15 @@ describe('failure tests’, function () { it('should next if authorization header is not Bearer and credentialsRequired is false’, function() { req.headers = {}; req.headers.authorization = 'Basic foobar’; expressjwt({secret: 'shhhh’, credentialsRequired: false})(req, res, function(err) { expressjwt({secret: 'shhhh’, algorithms: [‘HS256’], credentialsRequired: false})(req, res, function(err) { assert.ok(typeof err === ‘undefined’); }); });
it('should throw if authorization header is not well-formatted jwt’, function() { req.headers = {}; req.headers.authorization = 'Bearer wrongjwt’; expressjwt({secret: 'shhhh’})(req, res, function(err) { expressjwt({secret: 'shhhh’, algorithms: [‘HS256’]})(req, res, function(err) { assert.ok(err); assert.equal(err.code, ‘invalid_token’); }); @@ -80,7 +98,7 @@ describe('failure tests’, function () { it('should throw if jwt is an invalid json’, function() { req.headers = {}; req.headers.authorization = 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.yJ1c2VybmFtZSI6InNhZ3VpYXIiLCJpYXQiOjE0NzEwMTg2MzUsImV4cCI6MTQ3MzYxMDYzNX0.foo’; expressjwt({secret: 'shhhh’})(req, res, function(err) { expressjwt({secret: 'shhhh’, algorithms: [‘HS256’]})(req, res, function(err) { assert.ok(err); assert.equal(err.code, ‘invalid_token’); }); @@ -92,7 +110,7 @@ describe('failure tests’, function () {
req.headers = {}; req.headers.authorization = 'Bearer ' + token; expressjwt({secret: 'different-shhhh’})(req, res, function(err) { expressjwt({secret: 'different-shhhh’, algorithms: [‘HS256’] })(req, res, function(err) { assert.ok(err); assert.equal(err.code, ‘invalid_token’); assert.equal(err.message, ‘invalid signature’); @@ -101,11 +119,11 @@ describe('failure tests’, function () {
it('should throw if audience is not expected’, function() { var secret = 'shhhhhh’; var token = jwt.sign({foo: 'bar’, aud: 'expected-audience’}, secret); var token = jwt.sign({foo: 'bar’, aud: 'expected-audience’}, secret, { expiresIn: 500});
req.headers = {}; req.headers.authorization = 'Bearer ' + token; expressjwt({secret: 'shhhhhh’, audience: 'not-expected-audience’})(req, res, function(err) { expressjwt({secret: 'shhhhhh’, algorithms: [‘HS256’], audience: 'not-expected-audience’})(req, res, function(err) { assert.ok(err); assert.equal(err.code, ‘invalid_token’); assert.equal(err.message, ‘jwt audience invalid. expected: not-expected-audience’); @@ -118,7 +136,7 @@ describe('failure tests’, function () {
req.headers = {}; req.headers.authorization = 'Bearer ' + token; expressjwt({secret: 'shhhhhh’})(req, res, function(err) { expressjwt({secret: 'shhhhhh’, algorithms: [‘HS256’]})(req, res, function(err) { assert.ok(err); assert.equal(err.code, ‘invalid_token’); assert.equal(err.inner.name, ‘TokenExpiredError’); @@ -132,7 +150,7 @@ describe('failure tests’, function () {
req.headers = {}; req.headers.authorization = 'Bearer ' + token; expressjwt({secret: 'shhhhhh’, issuer: 'http://wrong’})(req, res, function(err) { expressjwt({secret: 'shhhhhh’, algorithms: [‘HS256’], issuer: 'http://wrong’})(req, res, function(err) { assert.ok(err); assert.equal(err.code, ‘invalid_token’); assert.equal(err.message, ‘jwt issuer invalid. expected: http://wrong’); @@ -141,14 +159,13 @@ describe('failure tests’, function () {
it('should use errors thrown from custom getToken function’, function() { var secret = 'shhhhhh’; var token = jwt.sign({foo: 'bar’}, secret);
function getTokenThatThrowsError() { throw new UnauthorizedError('invalid_token’, { message: ‘Invalid token!’ }); }
expressjwt({ secret: 'shhhhhh’, secret: 'shhhhhh’, algorithms: [‘HS256’], getToken: getTokenThatThrowsError })(req, res, function(err) { assert.ok(err); @@ -157,7 +174,6 @@ describe('failure tests’, function () { }); });
it('should throw error when signature is wrong’, function() { var secret = "shhh"; var token = jwt.sign({foo: 'bar’, iss: 'http://www’}, secret); @@ -170,7 +186,7 @@ describe('failure tests’, function () { // build request req.headers = []; req.headers.authorization = 'Bearer ' + newToken; expressjwt({secret: secret})(req,res, function(err) { expressjwt({secret: secret, algorithms: [‘HS256’]})(req,res, function(err) { assert.ok(err); assert.equal(err.code, ‘invalid_token’); assert.equal(err.message, ‘invalid token’); @@ -183,7 +199,7 @@ describe('failure tests’, function () {
req.headers = {}; req.headers.authorization = 'Bearer ' + token; expressjwt({ secret: secret, credentialsRequired: false })(req, res, function(err) { expressjwt({ secret: secret, credentialsRequired: false, algorithms: [‘HS256’] })(req, res, function(err) { assert.ok(err); assert.equal(err.code, ‘invalid_token’); assert.equal(err.message, ‘jwt expired’); @@ -196,7 +212,7 @@ describe('failure tests’, function () {
req.headers = {}; req.headers.authorization = 'Bearer ' + token; expressjwt({ secret: "not the secret", credentialsRequired: false })(req, res, function(err) { expressjwt({ secret: "not the secret", algorithms: [‘HS256’], credentialsRequired: false })(req, res, function(err) { assert.ok(err); assert.equal(err.code, ‘invalid_token’); assert.equal(err.message, ‘invalid signature’); @@ -215,7 +231,7 @@ describe('work tests’, function () {
req.headers = {}; req.headers.authorization = 'Bearer ' + token; expressjwt({secret: secret})(req, res, function() { expressjwt({secret: secret, algorithms: [‘HS256’]})(req, res, function() { assert.equal('bar’, req.user.foo); }); }); @@ -226,7 +242,7 @@ describe('work tests’, function () {
req.headers = {}; req.headers.authorization = 'Bearer ' + token; expressjwt({secret: secret, requestProperty: 'auth.token’})(req, res, function() { expressjwt({secret: secret, algorithms: [‘HS256’], requestProperty: 'auth.token’})(req, res, function() { assert.equal('bar’, req.auth.token.foo); }); }); @@ -237,7 +253,7 @@ describe('work tests’, function () {
req.headers = {}; req.headers.authorization = 'Bearer ' + token; expressjwt({secret: secret})(req, res, function() { expressjwt({secret: secret, algorithms: [‘HS256’]})(req, res, function() { assert.equal('bar’, req.user.foo); }); }); @@ -248,7 +264,7 @@ describe('work tests’, function () {
req.headers = {}; req.headers.authorization = 'Bearer ' + token; expressjwt({secret: secret, userProperty: 'auth’})(req, res, function() { expressjwt({secret: secret, algorithms: [‘HS256’], userProperty: 'auth’})(req, res, function() { assert.equal('bar’, req.auth.foo); }); }); @@ -261,7 +277,7 @@ describe('work tests’, function () { res = { }; req.headers = {}; req.headers.authorization = 'Bearer ' + token; expressjwt({secret: secret, resultProperty: 'locals.user’})(req, res, function() { expressjwt({secret: secret, algorithms: [‘HS256’], resultProperty: 'locals.user’})(req, res, function() { assert.equal('bar’, res.locals.user.foo); assert.ok(typeof req.user === ‘undefined’); }); @@ -275,22 +291,22 @@ describe('work tests’, function () { res = { }; req.headers = {}; req.headers.authorization = 'Bearer ' + token; expressjwt({secret: secret, userProperty: 'auth’, resultProperty: 'locals.user’})(req, res, function() { expressjwt({secret: secret, algorithms: [‘HS256’], userProperty: 'auth’, resultProperty: 'locals.user’})(req, res, function() { assert.equal('bar’, res.locals.user.foo); assert.ok(typeof req.auth === ‘undefined’); }); });
it('should work if no authorization header and credentials are not required’, function() { req = {}; expressjwt({ secret: 'shhhh’, credentialsRequired: false })(req, res, function(err) { expressjwt({ secret: 'shhhh’, algorithms: [‘HS256’], credentialsRequired: false })(req, res, function(err) { assert(typeof err === ‘undefined’); }); });
it('should not work if no authorization header’, function() { req = {}; expressjwt({ secret: ‘shhhh’ })(req, res, function(err) { expressjwt({ secret: 'shhhh’, algorithms: [‘HS256’] })(req, res, function(err) { assert(typeof err !== ‘undefined’); }); }); @@ -301,7 +317,7 @@ describe('work tests’, function () { req.headers = {}; req.headers.authorization = 'Bearer ' + token;
expressjwt({secret: 'secretB’})(req, res, function(err) { expressjwt({secret: 'secretB’, algorithms: [‘HS256’]})(req, res, function(err) { var index = err.stack.indexOf(‘UnauthorizedError: invalid signature’) assert.equal(index, 0, “Stack trace didn’t include ‘invalid signature’ message.”) }); @@ -322,6 +338,7 @@ describe('work tests’, function () {
expressjwt({ secret: secret, algorithms: [‘HS256’], getToken: getTokenFromQuery })(req, res, function() { assert.equal('bar’, req.user.foo); @@ -339,7 +356,7 @@ describe('work tests’, function () {
req.headers = {}; req.headers.authorization = 'Bearer ' + token; expressjwt({secret: secretCallback})(req, res, function() { expressjwt({secret: secretCallback, algorithms: [‘HS256’]})(req, res, function() { assert.equal('bar’, req.user.foo); }); });