tymondesigns / jwt-auth

🔐 JSON Web Token Authentication for Laravel & Lumen
https://jwt-auth.com
MIT License
11.24k stars 1.55k forks source link

Claims are re-used for second JWT #2143

Open little-apps opened 3 years ago

little-apps commented 3 years ago

Subject of the issue

When one JWT is generated with custom claims and then a second JWT is parsed or generated with no claims, the second JWT contains the claims from the first JWT. This could be considered a security vulnerability because an attacker could set the claims for the second JWT as well as have the second JWT bypass validation checks (as shown below).

Your environment

Q A
Bug? yes?
New Feature? no
Framework Laravel
Framework version 8.50.0
Package version dev-develop (ab00f2d7cce5f043067aef7849cdc792de2df635)
PHP version 8.0.7

Steps to reproduce

Generate a JWT from a subject and include custom claims:

use App\Models\User;
use Tymon\JWTAuth\Facades\JWTAuth;
use Tymon\JWTAuth\Claims\Custom;

$subject = new User(['id' => 1]);

$expiry = 1618263217; // Monday, April 12, 2021 9:33:37 PM

$token1 = JWTAuth::claims([new Custom('exp', $expiry)])->fromSubject($subject);

dump(JWTAuth::setToken($token1)->payload());

Output:

Tymon\JWTAuth\Payload^ {#3588
  -claims: Tymon\JWTAuth\Claims\Collection^ {#3615
    #items: array:8 [
      "iss" => Tymon\JWTAuth\Claims\Issuer^ {#3611
        #name: "iss"
        -value: "http://localhost"
      }
      "iat" => Tymon\JWTAuth\Claims\IssuedAt^ {#3590
        #name: "iat"
        -value: 1626903289
        #leeway: 0
      }
      "exp" => Tymon\JWTAuth\Claims\Custom^ {#3578
        #name: "exp"
        -value: 1618263217
      }
      "nbf" => Tymon\JWTAuth\Claims\NotBefore^ {#3614
        #name: "nbf"
        -value: 1626903289
        #leeway: 0
      }
      "jti" => Tymon\JWTAuth\Claims\JwtId^ {#3598
        #name: "jti"
        -value: "3Fp4N8XctImAdAiS"
      }
      "sub" => Tymon\JWTAuth\Claims\Subject^ {#3613
        #name: "sub"
        -value: null
      }
      "prv" => Tymon\JWTAuth\Claims\Custom^ {#3617
        #name: "prv"
        -value: "23bd5c8949f600adb39e701c400872db7a5976f7"
      }
      "fingerprint" => Tymon\JWTAuth\Claims\Custom^ {#3616
        #name: "fingerprint"
        -value: "0b70e6c4660abb7ec9308c0acb9d91294495c67f41a0bafbc0dafb4a881e901a"
      }
    ]
  }
}

Generate a second JWT with no custom claims:

$token2 = JWTAuth::claims([])->fromSubject($subject);

JWTAuth::setToken($token2)->checkOrFail();

dump(JWTAuth::payload());

Output:

Tymon\JWTAuth\Payload^ {#3629
  -claims: Tymon\JWTAuth\Claims\Collection^ {#3635
    #items: array:8 [
      "iss" => Tymon\JWTAuth\Claims\Issuer^ {#3615
        #name: "iss"
        -value: "http://localhost"
      }
      "iat" => Tymon\JWTAuth\Claims\IssuedAt^ {#3645
        #name: "iat"
        -value: 1626903364
        #leeway: 0
      }
      "exp" => Tymon\JWTAuth\Claims\Custom^ {#3578
        #name: "exp"
        -value: 1618263217
      }
      "nbf" => Tymon\JWTAuth\Claims\NotBefore^ {#3613
        #name: "nbf"
        -value: 1626903364
        #leeway: 0
      }
      "jti" => Tymon\JWTAuth\Claims\JwtId^ {#312
        #name: "jti"
        -value: "gvEpHxlSZPzC00aE"
      }
      "sub" => Tymon\JWTAuth\Claims\Subject^ {#3634
        #name: "sub"
        -value: null
      }
      "prv" => Tymon\JWTAuth\Claims\Custom^ {#3644
        #name: "prv"
        -value: "23bd5c8949f600adb39e701c400872db7a5976f7"
      }
      "fingerprint" => Tymon\JWTAuth\Claims\Custom^ {#3598
        #name: "fingerprint"
        -value: "0b70e6c4660abb7ec9308c0acb9d91294495c67f41a0bafbc0dafb4a881e901a"
      }
    ]
  }
}

Expected behaviour

The checkOrFail() method throws an exception and the claims are set to fresh instances.

Actual behaviour

The checkOrFail() method doesn't throw an exception and the expiry claim is still set to the first claim instance (with value 1618263217).

Messhias commented 2 years ago

@little-apps thanks for the contribution, but @tymondesigns seems quite absent for this repository and I don't know when he can back to help us, and seems his the only maintainer of the library :(