CVE-2015-9235: Use of a Broken or Risky Cryptographic Algorithm (CWE-327)
value: In jsonwebtoken node module before 4.2.2 it is possible for an attacker to bypass verification when a token digitally signed with an asymmetric key (RS/ES family) of algorithms but instead the attacker send a token digitally signed with a symmetric algorithm (HS* family).
CWE description: The product uses a broken or risky cryptographic algorithm or protocol.
CVSS v3 base score: 9.8
Published: 2018-05-29T20:29Z
Repository: https://github.com/auth0/node-jsonwebtoken
Language: JavaScript
Files changed: .jshintrc, index.js, test/wrong_alg.tests.js
Commit date: 2015-03-16 20:36:22 -0300
Commit message: Verification with an asymmetric key of a token signed with a symmetric key

There is a vulnerability in this module when the verification part is expecting a token digitally signed with an asymetric key (RS/ES family) of algorithms but instead the attacker send a token digitally signed with a symmetric algorithm (HS* family).

The issue is because this library has the very same signature to verify both type of tokens (parameter: `secretOrPublicKey`).

This change adds a new parameter to the verify called `algorithms`. This can be used to specify a list of supported algorithms, but the default value depends on the secret used: if the secretOrPublicKey contains the string `BEGIN CERTIFICATE` the default is `[ 'RS256','RS384','RS512','ES256','ES384','ES512' ]` otherwise is `[ 'HS256','HS384','HS512' ]`.

Diff: diff --git a/.jshintrc b/.jshintrc
index e0b1f93..0f9fef3 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -13,8 +13,9 @@
   "globals": {
     "describe": true,
     "it": true,
     "require": true,
     "atob": false,
-    "escape": true
+    "escape": true,
+    "before": true
   }
 }
\ No newline at end of file
diff --git a/index.js b/index.js
index 442fd50..7bd3d2d 100644
--- a/index.js
+++ b/index.js
@@ -105,10 +105,16 @@ module.exports.verify = function(jwtString, secretOrPublicKey, options, callback
 
   if (parts[2].trim() === '' && secretOrPublicKey){
     return done(new JsonWebTokenError('jwt signature is required'));
   }
 
+  if (!options.algorithms) {
+    options.algorithms = ~secretOrPublicKey.toString().indexOf('BEGIN CERTIFICATE') ?
+                        [ 'RS256','RS384','RS512','ES256','ES384','ES512' ] :
+                        [ 'HS256','HS384','HS512' ];
+  }
+
   var valid;
 
   try {
     valid = jws.verify(jwtString, secretOrPublicKey);
   } catch (e) {
@@ -124,10 +130,15 @@ module.exports.verify = function(jwtString, secretOrPublicKey, options, callback
     payload = this.decode(jwtString);
   } catch(err) {
     return done(err);
   }
 
+  var header = jws.decode(jwtString).header;
+  if (!~options.algorithms.indexOf(header.alg)) {
+    return done(new JsonWebTokenError('invalid signature'));
+  }
+
   if (typeof payload.exp !== 'undefined' && !options.ignoreExpiration) {
     if (typeof payload.exp !== 'number') {
       return done(new JsonWebTokenError('invalid exp value'));
     }
     if (Math.floor(Date.now() / 1000) >= payload.exp)
diff --git a/test/wrong_alg.tests.js b/test/wrong_alg.tests.js
new file mode 100644
index 0000000..513ba18
--- /dev/null
+++ b/test/wrong_alg.tests.js
@@ -0,0 +1,20 @@
+var fs = require('fs');
+var path = require('path');
+var jwt = require('../index');
+var JsonWebTokenError = require('../lib/JsonWebTokenError');
+var expect = require('chai').expect;
+
+
+var pub = fs.readFileSync(path.join(__dirname, 'pub.pem'), 'utf8');
+// priv is never used
+// var priv = fs.readFileSync(path.join(__dirname, 'priv.pem'));
+
+var TOKEN = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MjY1NDY5MTl9.ETgkTn8BaxIX4YqvUWVFPmum3moNZ7oARZtSBXb_vP4';
+
+describe('signing with pub key as symmetric', function () {
+  it('should not verify', function () {
+    expect(function () {
+      jwt.verify(TOKEN, pub);
+    }).to.throw(JsonWebTokenError, /invalid signature/);
+  });
+});
\ No newline at end of file

Vulnerable code:     "escape": true
Fixed code:     "escape": true,
    "before": true
  if (!options.algorithms) {
    options.algorithms = ~secretOrPublicKey.toString().indexOf('BEGIN CERTIFICATE') ?
                        [ 'RS256','RS384','RS512','ES256','ES384','ES512' ] :
                        [ 'HS256','HS384','HS512' ];
  }

  var header = jws.decode(jwtString).header;
  if (!~options.algorithms.indexOf(header.alg)) {
    return done(new JsonWebTokenError('invalid signature'));
  }

var fs = require('fs');
var path = require('path');
var jwt = require('../index');
var JsonWebTokenError = require('../lib/JsonWebTokenError');
var expect = require('chai').expect;


var pub = fs.readFileSync(path.join(__dirname, 'pub.pem'), 'utf8');
// priv is never used
// var priv = fs.readFileSync(path.join(__dirname, 'priv.pem'));

var TOKEN = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MjY1NDY5MTl9.ETgkTn8BaxIX4YqvUWVFPmum3moNZ7oARZtSBXb_vP4';

describe('signing with pub key as symmetric', function () {
  it('should not verify', function () {
    expect(function () {
      jwt.verify(TOKEN, pub);
    }).to.throw(JsonWebTokenError, /invalid signature/);
  });
});