VYPR
High severityNVD Advisory· Published Sep 1, 2020· Updated Sep 23, 2021

Forgeable Public/Private Tokens in jws

CVE-2016-1000223

Description

Affected versions of the jws package allow users to select what algorithm the server will use to verify a provided JWT. A malicious actor can use this behaviour to arbitrarily modify the contents of a JWT while still passing verification. For the common use case of the JWT as a bearer token, the end result is a complete authentication bypass with minimal effort.

Recommendation

Update to version 3.0.0 or later.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
jwsnpm
< 3.0.03.0.0

Patches

1
585d0e1e97b6

v3.0.0

https://github.com/brianloveswords/node-jwsBrian J BrennanApr 8, 2015via ghsa
5 files changed · +59 45
  • CHANGELOG.md+10 0 modified
    @@ -1,6 +1,16 @@
     # Change Log
     All notable changes to this project will be documented in this file.
     
    +## [3.0.0]
    +### Changed
    +- **BREAKING**: `jwt.verify` now requires an `algorithm` parameter, and
    +  `jws.createVerify` requires an `algorithm` option. The `"alg"` field
    +  signature headers is ignored. This mitigates a critical security flaw
    +  in the library which would allow an attacker to generate signatures with
    +  arbitrary contents that would be accepted by `jwt.verify`. See
    +  https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/
    +  for details.
    +
     ## [2.0.0] - 2015-01-30
     ### Changed
     - **BREAKING**: Default payload encoding changed from `binary` to
    
  • lib/verify-stream.js+9 22 modified
    @@ -27,25 +27,6 @@ function securedInputFromJWS(jwsSig) {
       return jwsSig.split('.', 2).join('.');
     }
     
    -function algoFromJWS(jwsSig) {
    -  var err;
    -  const header = headerFromJWS(jwsSig);
    -  if (typeof header != 'object') {
    -    err = new Error("Invalid token: no header in signature '" + jwsSig + "'");
    -    err.code = "MISSING_HEADER";
    -    err.signature = jwsSig;
    -    throw err;
    -  }
    -  if (!header.alg) {
    -    err = new Error("Missing `alg` field in header for signature '"+ jwsSig +"'");
    -    err.code = "MISSING_ALGORITHM";
    -    err.header = header;
    -    err.signature = jwsSig;
    -    throw err;
    -  }
    -  return header.alg;
    -}
    -
     function signatureFromJWS(jwsSig) {
       return jwsSig.split('.')[2];
     }
    @@ -60,11 +41,16 @@ function isValidJws(string) {
       return JWS_REGEX.test(string) && !!headerFromJWS(string);
     }
     
    -function jwsVerify(jwsSig, secretOrKey) {
    +function jwsVerify(jwsSig, algorithm, secretOrKey) {
    +  if (!algorithm) {
    +    var err = new Error("Missing algorithm parameter for jws.verify");
    +    err.code = "MISSING_ALGORITHM";
    +    throw err;
    +  }
       jwsSig = toString(jwsSig);
       const signature = signatureFromJWS(jwsSig);
       const securedInput = securedInputFromJWS(jwsSig);
    -  const algo = jwa(algoFromJWS(jwsSig));
    +  const algo = jwa(algorithm);
       return algo.verify(securedInput, signature, secretOrKey);
     }
     
    @@ -96,6 +82,7 @@ function VerifyStream(opts) {
       const secretOrKey = opts.secret||opts.publicKey||opts.key;
       const secretStream = new DataStream(secretOrKey);
       this.readable = true;
    +  this.algorithm = opts.algorithm;
       this.encoding = opts.encoding;
       this.secret = this.publicKey = this.key = secretStream;
       this.signature = new DataStream(opts.signature);
    @@ -111,7 +98,7 @@ function VerifyStream(opts) {
     }
     util.inherits(VerifyStream, Stream);
     VerifyStream.prototype.verify = function verify() {
    -  const valid = jwsVerify(this.signature.buffer, this.key.buffer);
    +  const valid = jwsVerify(this.signature.buffer, this.algorithm, this.key.buffer);
       const obj = jwsDecode(this.signature.buffer, this.encoding);
       this.emit('done', valid, obj);
       this.emit('data', valid);
    
  • package.json+1 1 modified
    @@ -1,6 +1,6 @@
     {
       "name": "jws",
    -  "version": "2.0.0",
    +  "version": "3.0.0",
       "description": "Implementation of JSON Web Signatures",
       "main": "index.js",
       "directories": {
    
  • readme.md+6 2 modified
    @@ -62,15 +62,18 @@ const signature = jws.sign({
     });
     ```
     
    -## jws.verify(signature, secretOrKey)
    +## jws.verify(signature, algorithm, secretOrKey)
     
     (Synchronous) Returns`true` or `false` for whether a signature matches a
     secret or key.
     
    -`signature` is a JWS Signature. `secretOrKey` is a string or
    +`signature` is a JWS Signature. `header.alg` must be a value found in `jws.ALGORITHMS`.
    +See above for a table of supported algorithms. `secretOrKey` is a string or
     buffer containing either the secret for HMAC algorithms, or the PEM
     encoded public key for RSA and ECDSA.
     
    +Note that the `"alg"` value from the signature header is ignored.
    +
     
     ## jws.decode(signature)
     
    @@ -127,6 +130,7 @@ Returns a new VerifyStream object.
     Options:
     
     * `signature`
    +* `algorithm`
     * `key` || `publicKey` || `secret`
     * `encoding` (Optional, defaults to 'utf8')
     
    
  • test/jws.test.js+33 20 modified
    @@ -46,7 +46,8 @@ const payload = {
     
     BITS.forEach(function (bits) {
       test('HMAC using SHA-'+bits+' hash algorithm', function (t) {
    -    const header = { alg: 'HS'+bits, typ: 'JWT' };
    +    const alg = 'HS'+bits;
    +    const header = { alg: alg, typ: 'JWT' };
         const secret = 'sup';
         const jwsObj = jws.sign({
           header: header,
    @@ -55,8 +56,9 @@ BITS.forEach(function (bits) {
           encoding: 'utf8',
         });
         const parts = jws.decode(jwsObj);
    -    t.ok(jws.verify(jwsObj, secret), 'should verify');
    -    t.notOk(jws.verify(jwsObj, 'something else'), 'should not verify');
    +    t.ok(jws.verify(jwsObj, alg, secret), 'should verify');
    +    t.notOk(jws.verify(jwsObj, alg, 'something else'), 'should not verify with non-matching secret');
    +    t.notOk(jws.verify(jwsObj, 'RS'+bits, secret), 'should not verify with non-matching algorithm');
         t.same(parts.payload, payload, 'should match payload');
         t.same(parts.header, header, 'should match header');
         t.end();
    @@ -65,7 +67,8 @@ BITS.forEach(function (bits) {
     
     BITS.forEach(function (bits) {
       test('RSASSA using SHA-'+bits+' hash algorithm', function (t) {
    -    const header = { alg: 'RS'+bits };
    +    const alg = 'RS'+bits;
    +    const header = { alg: alg };
         const privateKey = rsaPrivateKey;
         const publicKey = rsaPublicKey;
         const wrongPublicKey = rsaWrongPublicKey;
    @@ -75,8 +78,9 @@ BITS.forEach(function (bits) {
           privateKey: privateKey
         });
         const parts = jws.decode(jwsObj, { json: true });
    -    t.ok(jws.verify(jwsObj, publicKey), 'should verify');
    -    t.notOk(jws.verify(jwsObj, wrongPublicKey), 'should not verify');
    +    t.ok(jws.verify(jwsObj, alg, publicKey), 'should verify');
    +    t.notOk(jws.verify(jwsObj, alg, wrongPublicKey), 'should not verify with non-matching public key');
    +    t.notOk(jws.verify(jwsObj, 'HS'+bits, publicKey), 'should not verify with non-matching algorithm');
         t.same(parts.payload, payload, 'should match payload');
         t.same(parts.header, header, 'should match header');
         t.end();
    @@ -86,7 +90,8 @@ BITS.forEach(function (bits) {
     BITS.forEach(function (bits) {
       const curve = CURVES[bits];
       test('ECDSA using P-'+curve+' curve and SHA-'+bits+' hash algorithm', function (t) {
    -    const header = { alg: 'ES'+bits };
    +    const alg = 'ES'+bits;
    +    const header = { alg: alg };
         const privateKey = ecdsaPrivateKey['256'];
         const publicKey = ecdsaPublicKey['256'];
         const wrongPublicKey = ecdsaWrongPublicKey['256'];
    @@ -96,24 +101,27 @@ BITS.forEach(function (bits) {
           privateKey: privateKey
         });
         const parts = jws.decode(jwsObj);
    -    t.ok(jws.verify(jwsObj, publicKey), 'should verify');
    -    t.notOk(jws.verify(jwsObj, wrongPublicKey), 'should not verify');
    +    t.ok(jws.verify(jwsObj, alg, publicKey), 'should verify');
    +    t.notOk(jws.verify(jwsObj, alg, wrongPublicKey), 'should not verify with non-matching public key');
    +    t.notOk(jws.verify(jwsObj, 'HS'+bits, publicKey), 'should not verify with non-matching algorithm');
         t.same(parts.payload, payloadString, 'should match payload');
         t.same(parts.header, header, 'should match header');
         t.end();
       });
     });
     
     test('No digital signature or MAC value included', function (t) {
    -  const header = { alg: 'none' };
    +  const alg = 'none';
    +  const header = { alg: alg };
       const payload = 'oh hey José!';
       const jwsObj = jws.sign({
         header: header,
         payload: payload,
       });
       const parts = jws.decode(jwsObj);
    -  t.ok(jws.verify(jwsObj), 'should verify');
    -  t.ok(jws.verify(jwsObj, 'anything'), 'should still verify');
    +  t.ok(jws.verify(jwsObj, alg), 'should verify');
    +  t.ok(jws.verify(jwsObj, alg, 'anything'), 'should still verify');
    +  t.notOk(jws.verify(jwsObj, 'HS256', 'anything'), 'should not verify with non-matching algorithm');
       t.same(parts.payload, payload, 'should match payload');
       t.same(parts.header, header, 'should match header');
       t.end();
    @@ -128,7 +136,7 @@ test('Streaming sign: HMAC', function (t) {
       });
       dataStream.pipe(sig.payload);
       sig.on('done', function (signature) {
    -    t.ok(jws.verify(signature, secret), 'should verify');
    +    t.ok(jws.verify(signature, 'HS256', secret), 'should verify');
         t.end();
       });
     });
    @@ -148,8 +156,8 @@ test('Streaming sign: RSA', function (t) {
       });
     
       sig.on('done', function (signature) {
    -    t.ok(jws.verify(signature, publicKey), 'should verify');
    -    t.notOk(jws.verify(signature, wrongPublicKey), 'should not verify');
    +    t.ok(jws.verify(signature, 'RS256', publicKey), 'should verify');
    +    t.notOk(jws.verify(signature, 'RS256', wrongPublicKey), 'should not verify');
         t.same(jws.decode(signature).payload, readfile('data.txt'), 'got all the data');
         t.end();
       });
    @@ -166,8 +174,8 @@ test('Streaming sign: RSA, predefined streams', function (t) {
         privateKey: privateKeyStream
       });
       sig.on('done', function (signature) {
    -    t.ok(jws.verify(signature, publicKey), 'should verify');
    -    t.notOk(jws.verify(signature, wrongPublicKey), 'should not verify');
    +    t.ok(jws.verify(signature, 'RS256', publicKey), 'should verify');
    +    t.notOk(jws.verify(signature, 'RS256', wrongPublicKey), 'should not verify');
         t.same(jws.decode(signature).payload, readfile('data.txt'), 'got all the data');
         t.end();
       });
    @@ -182,7 +190,7 @@ test('Streaming verify: ECDSA', function (t) {
         payload: dataStream,
         privateKey: privateKeyStream
       });
    -  const verifier = jws.createVerify();
    +  const verifier = jws.createVerify({algorithm: 'ES512'});
       sigStream.pipe(verifier.signature);
       publicKeyStream.pipe(verifier.key);
       verifier.on('done', function (valid) {
    @@ -201,6 +209,7 @@ test('Streaming verify: ECDSA, with invalid key', function (t) {
         privateKey: privateKeyStream
       });
       const verifier = jws.createVerify({
    +    algorithm: 'ES512',
         signature: sigStream,
         publicKey: publicKeyStream,
       });
    @@ -225,14 +234,18 @@ test('jws.decode: with a bogus header ', function (t) {
       t.end();
     });
     
    -test('jws.decode: missing algo in header', function (t) {
    +test('jws.verify: missing or invalid algorithm', function (t) {
       const header = Buffer('{"something":"not an algo"}').toString('base64');
       const payload = Buffer('sup').toString('base64');
       const sig = header + '.' + payload + '.';
    -  try { jws.verify(sig, 'whatever') }
    +  try { jws.verify(sig) }
       catch (e) {
         t.same(e.code, 'MISSING_ALGORITHM');
       }
    +  try { jws.verify(sig, 'whatever') }
    +  catch (e) {
    +    t.ok(e.message.match('"whatever" is not a valid algorithm.'));
    +  }
       t.end();
     });
     
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.