CVE-2021-41580: Other (NVD-CWE-Other)
value: The passport-oauth2 package before 1.6.1 for Node.js mishandles the error condition of failure to obtain an access token. This is exploitable in certain use cases where an OAuth identity provider uses an HTTP 200 status code for authentication-failure error reports, and an application grants authorization upon simply receiving the access token (i.e., does not try to use the token). NOTE: the passport-oauth2 vendor does not consider this a passport-oauth2 vulnerability
CWE description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
CVSS v3 base score: 5.3
Published: 2021-09-27T07:15Z
Repository: https://github.com/jaredhanson/passport-oauth2
Language: JavaScript
Files changed: lib/strategy.js, test/oauth2.test.js
Commit date: 2021-09-24 14:26:10 -0700
Commit message: Add test case and avoid parsing internal OAuth error when it doesn't exist.

Diff: diff --git a/lib/strategy.js b/lib/strategy.js
index 60ad54a..b05aacc 100644
--- a/lib/strategy.js
+++ b/lib/strategy.js
@@ -172,11 +172,12 @@ OAuth2Strategy.prototype.authenticate = function(req, options) {
         params.code_verifier = ok;
       }
 
       self._oauth2.getOAuthAccessToken(code, params,
         function(err, accessToken, refreshToken, params) {
-          if (err || !accessToken) { return self.error(self._createOAuthError('Failed to obtain access token', err)); }
+          if (err) { return self.error(self._createOAuthError('Failed to obtain access token', err)); }
+          if (!accessToken) { return self.error(new Error('Failed to obtain access token')); }
 
           self._loadUserProfile(accessToken, function(err, profile) {
             if (err) { return self.error(err); }
 
             function verified(err, user, info) {
diff --git a/test/oauth2.test.js b/test/oauth2.test.js
index e829751..36552b6 100644
--- a/test/oauth2.test.js
+++ b/test/oauth2.test.js
@@ -1254,10 +1254,49 @@ describe('OAuth2Strategy', function() {
         expect(err.oauthError.statusCode).to.equal(500);
         expect(err.oauthError.data).to.equal('Something went wrong');
       });
     }); // that errors due to token request error, in node-oauth object literal form with text body
     
+    describe('that errors due to not receiving an access token', function() {
+      var strategy = new OAuth2Strategy({
+        authorizationURL: 'https://www.example.com/oauth2/authorize',
+        tokenURL: 'https://www.example.com/oauth2/token',
+        clientID: 'ABC123',
+        clientSecret: 'secret',
+        callbackURL: 'https://www.example.net/auth/example/callback',
+      },
+      function(accessToken, refreshToken, params, profile, done) {
+        return done(new Error('something went wrong'));
+      });
+  
+      strategy._oauth2.getOAuthAccessToken = function(code, options, callback) {
+        return callback(null, undefined, undefined, undefined);
+      }
+  
+  
+      var err;
+  
+      before(function(done) {
+        chai.passport.use(strategy)
+          .error(function(e) {
+            err = e;
+            done();
+          })
+          .req(function(req) {
+            req.query = {};
+            req.query.code = 'SplxlOBeZQQYbYS6WxSbIA';
+          })
+          .authenticate();
+      });
+
+      it('should error', function() {
+        expect(err).to.be.an.instanceof(Error);
+        expect(err).to.not.be.an.instanceof(InternalOAuthError)
+        expect(err.message).to.equal('Failed to obtain access token');
+      });
+    }); // that errors due to not receiving an access token
+    
     describe('that errors due to verify callback supplying error', function() {
       var strategy = new OAuth2Strategy({
         authorizationURL: 'https://www.example.com/oauth2/authorize',
         tokenURL: 'https://www.example.com/oauth2/token',
         clientID: 'ABC123',

Vulnerable code:           if (err || !accessToken) { return self.error(self._createOAuthError('Failed to obtain access token', err)); }
Fixed code:           if (err) { return self.error(self._createOAuthError('Failed to obtain access token', err)); }
          if (!accessToken) { return self.error(new Error('Failed to obtain access token')); }
    describe('that errors due to not receiving an access token', function() {
      var strategy = new OAuth2Strategy({
        authorizationURL: 'https://www.example.com/oauth2/authorize',
        tokenURL: 'https://www.example.com/oauth2/token',
        clientID: 'ABC123',
        clientSecret: 'secret',
        callbackURL: 'https://www.example.net/auth/example/callback',
      },
      function(accessToken, refreshToken, params, profile, done) {
        return done(new Error('something went wrong'));
      });
  
      strategy._oauth2.getOAuthAccessToken = function(code, options, callback) {
        return callback(null, undefined, undefined, undefined);
      }
  
  
      var err;
  
      before(function(done) {
        chai.passport.use(strategy)
          .error(function(e) {
            err = e;
            done();
          })
          .req(function(req) {
            req.query = {};
            req.query.code = 'SplxlOBeZQQYbYS6WxSbIA';
          })
          .authenticate();
      });

      it('should error', function() {
        expect(err).to.be.an.instanceof(Error);
        expect(err).to.not.be.an.instanceof(InternalOAuthError)
        expect(err.message).to.equal('Failed to obtain access token');
      });
    }); // that errors due to not receiving an access token