In my previous post “what abaout: JWT” we looked at what JWT actually is and how it is used. We only looked at the basics, there are countless articles out there that cover this topic in detail. A very good article can be found at Auth0.
Now we want to look at where the weaknesses of such a token, or rather the underlying implementations, may lie and how they can be exploited. In this blog we will only look at standard attacks, the blog “Attack: JWT issuer forgery” takes the attack to the extreme. These standard attacks do not require any additional infrastructure to work.
To recap, a JWT consists of a header, a payload and a signature. The signature confirms the integrity of the token and that it has not been tampered with. Only RS256 tokens can be independently verified (if the public key is available), while for HS256 tokens this is not possible in principle (because the passphaser is not known).
Let’s say an IdP issues a token that we want to use for authentication with a third-party API. This happens all the time with Azure App Tokens, for example. Azure AD issues a token that is then used to authenticate to the Graph API or the SharePoint Online API. The target API should, in its own interest, first check that the token has not changed since it was issued. For example, have new roles been added or has the user context changed? The easiest way for the API to do this is to check that the signature on the token is still valid. If it is not, the API should respond with a 403 Forbidden and write the necessary logs.
Signature attack
There are always implementations in the wild, through frameworks or custom developments, that do not check the signature. This is our first attack focus. Let’s take the following token as an example, issued by a legitimate IdP:
{
"typ": "JWT",
"alg": "HS256"
}.{
"iss": "https://idp.collfuse.com/",
"iat": 1682924253,
"exp": 1714460253,
"aud": "api.collfuse.com",
"sub": "[email protected]",
"GivenName": "Harvey",
"Surname": "Specter",
"Email": "[email protected]",
"Role": "User",
"user_id": "34877-34543-23446-24334",
"is_admin": "0"
}.[Signature]
The encoded Version would look like this
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2lkcC5jb2xsZnVzZS5jb20vIiwiaWF0IjoxNjgyOTI0MjUzLCJleHAiOjE3MTQ0NjAyNTMsImF1ZCI6ImFwaS5jb2xsZnVzZS5jb20iLCJzdWIiOiJqd3RAY29sbGZ1c2UuY29tIiwiR2l2ZW5OYW1lIjoiSGFydmV5IiwiU3VybmFtZSI6IlNwZWN0ZXIiLCJFbWFpbCI6ImhhcnZleS5zcGVjdGVyQGNvbGxmdXNlLmNvbSIsIlJvbGUiOiJVc2VyIiwidXNlcl9pZCI6IjM0ODc3LTM0NTQzLTIzNDQ2LTI0MzM0IiwiaXNfYWRtaW4iOiIwIn0.x6FOhneJSWDU19agOwv7zmoR1Irp6JEdVAgnSMruR5o
If the target system verifies the signature or not, we can find out very quickly. We don’t have to make any effort to generate a new header or payload. All we do is change one character before the last delimiter (the dot). So the signature is already invalid and the target system should reject the token. In the following example it would be the yellow “C” in the payload section that we have replaced.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2lkcC5jb2xsZnVzZS5jb20vIiwiaWF0IjoxNjgyOTI0MjUzLCJleHAiOjE3MTQ0NjAyNTMsImF1ZCI6ImFwaS5jb2xsZnVzZS5jb20iLCJzdWIiOiJqd3RAY29sbGZ1c2UuY29tIiwiRCl2ZW5OYW1lIjoiSGFydmV5IiwiU3VybmFtZSI6IlNwZWN0ZXIiLCJFbWFpbCI6ImhhcnZleS5zcGVjdGVyQGNvbGxmdXNlLmNvbSIsIlJvbGUiOiJVc2VyIiwidXNlcl9pZCI6IjM0ODc3LTM0NTQzLTIzNDQ2LTI0MzM0IiwiaXNfYWRtaW4iOiIwIn0.x6FOhneJSWDU19agOwv7zmoR1Irp6JEdVAgnSMruR5o
The next step is to send the modified token to the service, if the application does not return an HTTP 403 or other error message, the service is vulnerable to the signature attack, jackpot. Now that we know that the service does not check the signature, we can modify or create our own token. This can easily be done in PowerShell, but if you like it even easier, you can use an online JWT builder that allows you to do this in a web GUI.
The possibilities of this attack are unlimited and allow a complete takeover of the application. The most important thing now is to find out how the application works. For example, on the token above, what is the administrator role called? Does it require a combination of is_admin and user_id? This is where your creativity is needed, and be aware that the application is most likely writing logs. I assume you understand what I mean and don’t need to say any more.
Header Claim attack
In principle, if a service verifies the signature, it becomes quite difficult to forge a token. However, we will look at one way of doing this in this blog. For now, let us look briefly at a “header claim attack”.
In many frameworks, a vulnerability was implemented that simply no one had considered before. The frameworks had implemented a signature check that worked cleanly. First, the token was decoded and the “alg” claim was read. If it was RS256, the signature was verified against the public key. So far so good, but the whole verification could be bypassed by changing the value from “RS256” (or any other possible value) to “none”. This way the verification function was never called and the signature was not verified. This is still possible in the wild if the frameworks have not been updated, so always worth a try.
Weak Passphaser
You can also try to crack a HS256 token with a brute force attack. This can be done using the JWT-cracker tool, which does nothing more than try to find the passphaser used to create the signature, similar to Jack the Ripper for hashed passwords.
However, it is important to note that this is not a weakness of the JWT itself, but of the way in which it is used. A chain is only as strong as its weakest link.
Conclusion
Yes, JWTs are secure, but only if they are implemented securely. As with any system, there are ways to break the lock and gain unauthorised access. It is always important to check your application for possible vulnerabilities as they are reported by the community, and to keep your frameworks up to date. If you follow these basic rules, you can sleep peacefully.
That’s it so far, stay tuned and see you soon!
** midjourney string “A hacker try to forgery an issuer token in a dark office by night, The screen lights up its face and in the background servers can be seen, a Visual Studio code is visible on the screen, cinematic lighting –ar 1: 2“