Dylan Ayrey

The Dig

May 17, 2024

Stop Recommending JWTs (with symmetric keys)

Stop Recommending JWTs (with symmetric keys)

Dylan Ayrey

May 17, 2024

JSON Web Tokens or JWTs were first introduced in 2015 as a cryptographic alternative to classical API keys. They advertised the following: no more database transactions, stateless identity validation, and decentralized identity validation.

To accomplish this, JWTs championed a few different algorithms to sign/hmac a token via:

  • Symmetric cryptography

  • Asymmetric cryptography

  • No cryptography (none)

This post focuses on the popular symmetric cryptography choice, and our analysis of its implementation in the wild. We may do a future post on the other algorithms, but here’s our top level findings on symmetric keys:

>1.2% of symmetric keys in production are guessable within seconds

Symmetric keys used to sign a JWT should always be randomly generated, but we found an alarming percentage of guessable passwords used instead of securely and randomly generated keys. This completely undermines the authentication and authorization security advertised by JWTs.

An attacker with access to the symmetric key used to sign a JWT can forge JWTs with any claim they want, including impersonating other users, impersonating admins, and anything else they’d like to do. 

Here were the most popular symmetric keys used:



As the entire industry pushes to go passwordless, symmetric key JWTs create a small pocket of code development where the weakness of passwords can creep in, in such a way that undermines the entire security of authentication and authorization.

Many of the symmetric keys used in production ripped the examples from public documentation, for example the string “your-256-bit-secret” being used in 2.3% of guessed keys. This is the string used in the jwt.io documentation explaining what JWT’s are.

Methodology

We sampled approximately 10,000 JWTs discovered on shodan.io, and an additional 500 committed to source code on GitHub:



To build a list of JWTs on Shodan, we searched for the term: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9`, which decodes to `{"alg": "HS256","typ": "JWT"}`.

This is an extremely common JWT header value, which indicates that the enclosed JWT uses the symmetric algorithm HS256 ( HMAC with SHA256).

The JWTs returned via Shodan were a small sample of all JWTs available on the public internet, because Shodan only catalogs JWTs returned to unauthenticated users on the first page view of the website. 



The JWTs committed to GitHub often lived in documentation, test files, and other sources unlikely to be used in production code. This also aligns with the juxtaposition of how much more guessable their symmetric keys ended up being, as we’ll see below.

To guess JWTs keys, we loaded them into Hashcat on a g2-standard-48 in GCP which costs about $1 / hour to run.



We then used Hashcat (which is optimized to run on GPU’s) to guess 1.4 billion symmetric keys per JWT, amounting to a whopping 11 trillion total guesses made.


We used a popular password list for the guesses, making the assumption many developers would use passwords instead of randomly generated symmetric keys. Here was the final command used:


hashcat shodan.txt -m 16500 passwords.txt


Note: this is a purposefully naive approach; more sophisticated password crackers augment base password lists with a rule set to make many more guesses (which we didn’t do).


The cost to make 1.4 billion guesses is ~$0.0006 per JWT in cloud compute.


After running Hashcat against all 10,000 JWTs discovered on Shodan, we plotted the most frequently seen symmetric keys:



And here are the results from the GitHub dataset:

Output image

Note: “Other” represents a collection of all JWT symmetric keys that we successfully guessed, but were only seen once. They were grouped together due to the low sample size.

Note the diversity in key types from the public internet, vs the ones found on GitHub.

Most JWTs committed to source code are not actually used in production


Given the high probability of developers picking JWT symmetric keys in production that can be guessed, we find it difficult to recommend them for general purpose usage.

This further validates the hypothesis that the majority of keys on GitHub are test, example, or documentation keys.

Detection challenges

TruffleHog has long-resisted open sourcing a generic JWT detector. While we have a wide range of JWT detectors for specific providers, creating a generic one is challenging.

Generally speaking, TruffleHog only detects keys that it can automatically validate because the majority of keys discovered are no longer valid, and this validation process eliminates false positives. Additionally most keys we report will stay active after reporting if there isn’t accountability to monitor the active state of the keys. This reinforces the need to validate if the keys are production keys or not, which is a challenge.

This research re-affirmed the challenges of detecting generic JWTs, as the ratio of JWTs with guessed keys in source code, to guessed JWTs in production is about 1 in 20. This would imply most of the keys in source code are not sensitive, and would just be false positives.

That means if we alerted developers every time we detected a JWT in source code, it only has 1 in 20 odds of being a production key, vs an example, test, or documentation key.

This signal to noise ratio is not acceptable at face value; however, there are other ways we’ve found to validate cryptographic keys in the past, so we haven’t given up hope for a high signal generic JWT detector just yet.

Why is this happening?

Of course you shouldn’t use weak passwords as your symmetric key, and yet it seems all too common. 

One answer as to why, is the most popular documentation for JWTs (hosted by auth0) for many years actually used a very weak password in their interactive example:


JWT Documentation in 2015



JWT Documentation in 2022


This change was made to nudge developers to use a 256 bit randomly generated secret. That being said, ironically, the string “your-256-bit-secret” is still a password, and we were able to crack some JWTs with this string:



But that’s not the whole story. Take a look at the most popular JWT libraries:


NodeJS


Python


Golang


Ruby


We found at least one production JWT using each of those default symmetric keys!

When the documentation from many of the popular JWT libraries provide examples nudging developers to use passwords instead of securely-generated random strings, that’s likely one of the key reasons (pun intended).

Conclusion

It’s difficult to unconditionally recommend Symmetric key JWTs today, given the current state of documentation and the overwhelming evidence that developers copy/paste default symmetric keys. That’s not to say you can’t securely implement HS256, but when making a recommendation to a large population of developers, one has to consider what the least security-minded of the group will do, rather than focusing on the most security-aware developers, or even average developers.

Given the gravity of selecting a weak symmetric key (the complete undermining of authz and authn within your web application) and the frequency of occurrence, today, in 2024, we cannot recommend developers use HS256 JWTs.