Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [day] [month] [year] [list]
Date: Wed, 11 Aug 2021 06:36:25 -0400
From: Paragon Initiative Enterprises Security Team <security@...agonie.com>
To: oss-security@...ts.openwall.com
Cc: fulldisclosure@...lists.org
Subject: firebase/php-jwt Algorithm Confusion with Key IDs

__Background__

Once upon a time, the Auth0 team demonstrated several attacks against JWT
libraries that are still found to this day. You can read about their
research here:
https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/

Or for a more fun spin on the issue, you can just check
https://www.howmanydayssinceajwtalgnonevuln.com

The two issues that were identified there were alg=none and substituting
HMAC over an asymmetric alg header (RS256, PS256, ES256, etc.). We like to
distinguish the two by referring to the alg=none as a Downgrade Attack, and
the other as Algorithm Confusion (even though, strictly speaking, they're
both examples of Algorithm Confusion).

Now let's talk about Firebase's PHP-JWT Library.

__PHP-JWT__

To their credit, the Firebase team attempts to side-step the Algorithm
Confusion issue:
https://github.com/firebase/php-jwt/blob/d2113d9b2e0e349796e72d2a63cf9319100382d2/src/JWT.php#L103-L108

The above code will check the alg header of a token and make sure it's in
the allow-list of acceptable algorithm identifiers. If the developer
talking to this library hard-codes support for only RS256, you can't just
swap an alg header.

But then there's this:
https://github.com/firebase/php-jwt/blob/d2113d9b2e0e349796e72d2a63cf9319100382d2/src/JWT.php#L114-L123

If you provide a `kid` header, the key is retrieved from the associative
array. Since keys are just strings (which means your HMAC keys can be
extremely weak passwords), there is no mechanism to prevent a very silly
form of misuse.

Imagine you have two different endpoints. (This can also work against
middleware, but two different endpoints is easier to visualize.)

The first endpoint expects HS256 tokens, because of some JWT-based
middleware (e.g. tamper-proof session IDs), and rejects any other algorithm.
The second is an OAuth2/OIDC processor that expects RS256, and rejects any
other algorithm.

Now imagine you implement these requirements with a common PHP framework
design: Dependency injection of a configuration object in both places
derived from a single file.

If you have the same map of "key id" => "key material" in both endpoints,
then attacking this is trivial: Instead of swapping the alg header, just
swap the kid header, and now you've confused an asymmetric public key for a
symmetric shared key. Oops!

Github issue: https://github.com/firebase/php-jwt/issues/351
Proposed patch: https://github.com/firebase/php-jwt/pull/352
Proof of Concept (demo app with exploit code):
https://github.com/firebase/php-jwt/files/6966712/php-jwt-poc.zip

__Timeline__

2021-08-03 - Issue identified in response to a Reddit question (link:
https://www.reddit.com/r/PHP/comments/owuuem/paseto_v200_released_lengthy_release_notes/h7me0p2/
)
2021-08-04 - Given no security vulnerability reporting information on the
firebase/php-jwt repository, we published it on a Github issue
2021-08-04 - Pull request with patch sent
2021-08-06 - Github issue bumped, still no response
2021-08-10 - We identify and notify several of the more than 1100 open
source PHP libraries that depend on firebase/php-jwt that may be affected
by this issue
2021-08-11 - Proof-of-Concept shared on the Github issue
2021-08-11 - Immediate mitigation published at
https://github.com/paragonie/php-jwt-guard
2021-08-11 - Email sent to oss-security@ and fulldisclosure@

__Mitigations__

If you aren't using this library in the exact way that's vulnerable, it's a
non-issue. But if you are, it could be a critical vulnerability in your
application. Not fun.

For the time being, consider only supporting one cryptography key in each
distinct JWT::decode() code path.

If you MUST use Key IDs with JWT, we wrote a library that wraps
Firebase's library and applies the security mitigation we proposed in #352:
https://github.com/paragonie/php-jwt-guard

We will maintain our wrapper library for as long as we have to, but if the
PHP community ends up not needing it, all the better.

__Long-Term Remediation__

If you don't need JWT in particular, consider PASETO for your applications
instead:

- https://github.com/paragonie/paseto
-
https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/03-Algorithm-Lucidity.md

If you need JWT because of compatibility with a third party, look into
https://github.com/lcobucci/jwt instead of firebase/php-jwt.

That's all from us right now.

Security Team
Paragon Initiative Enterprises <https://paragonie.com/security>

Powered by blists - more mailing lists

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.