JWT - Vulnerabilities, Attacks & Security Best Practices
JWT - Vulnerabilities, Attacks & Security Best Practices
TECHNICAL
Increasingly used, JWTs are not, however, free of vulnerabilities, and poor
implementation can expose applications to potentially disastrous attacks.
In this article, we’ll take a closer look at how JWTs work. We will also re-
view the common vulnerabilities associated with JWTs, attack techniques and
best practices for configuration and implementation to minimise these risks.
What is JWT?
Payload
Signature
jwk
jku
kid
Algorithm Confusion
Conclusion
What is JWT?
As specified in RFC 7519, a JSON Web Token (JWT) is a compact format for rep-
resenting information, known as ‘claims’, exchanged between two parties. These
claims are encapsulated in a JSON object which constitutes the content (or
payload) of a signing (JWS – JSON Web Signature) or encrypted (JWE – JSON Web
Encryption) structure.
In other words, JWT is a standard that can be implemented in two ways: either
via a JWS, or via a JWE.
With JWS, the data is not encrypted but digitally signed. This guarantees its
integrity: the content remains readable, but only a holder of the secret can
generate a valid signing. This is the most commonly used form of JWT.
Since JWS is by far the most widespread implementation, the rest of this arti-
cle will be based on this format. For the sake of simplicity, we will use the
term JWT even when it is actually a JWS.
JWTs are generated by the server when a user authenticates to a web applica-
tion. Once issued, these tokens are sent to the client, which automatically
attaches them to each subsequent HTTP request. This enables the server to
identify the user at each interaction, without requiring any further
authentication.
The token contains information encoded and signed using a private key held by
the server. Each time a JWT is received, the server can check its authenticity
by recalculating the signing using its key and comparing it with the signature
in the token. If the two signatures match, the JWT is considered valid.
• The payload:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaXNBZG1pbiI6ZmFsc2V9
Each part is separated by a ‘.’ with the JOSE header and payload encoded in
base64url. The signature, meanwhile, is generated based on a secret or a pair
of cryptographic keys.
JOSE header
{
"alg": "HS256",
"typ": "JWT"
}
The JOSE header of a JWT contains two key parameters: alg and typ . The alg
parameter indicates the signing algorithm used, while typ specifies the type
of token. Only alg is mandatory, but other optional parameters can also be
added as required. Some of these will be discussed later in this article.
Payload
{
"sub": "1234567890",
"name": "John Doe",
"isAdmin": false
}
The payload of a JWT contains what are known as claims, i.e. the information
that the token carries. In our example, the claims are sub , name and
isAdmin .
• Public claims, which are standardised and listed in the IANA ‘JSON Web
Token Claims’ registry to avoid name collisions.
There are many other claims defined in the RFC, but none are strictly manda-
tory. To find out more, please consult the dedicated section of RFC 7519.
Signature
The signature is the element that gives the JWT its security. It is calculated
from the JOSE header, the payload and a secret known only to the server.
Thanks to this mechanism, any modification of the token (for example by an at-
tacker trying to falsify the data) would result in an invalid signature. The
server, by recalculating the signature with the secret, will then detect the
inconsistency and reject the JWT. This is why it is crucial that this secret
remains confidential: if it were to leak, an attacker could generate perfectly
valid fraudulent tokens.
The signature can be generated using various algorithms defined in the JWA
specification – JSON Web Algorithms. Depending on the algorithm used, the se-
cret can be:
•
Symmetric, with the same shared key for signing and verification (e.g.
HS256)
In the case of an asymmetric key, the public key can be exposed by the server
in a standardised format defined in the JWK – JSON Web Key specification. This
enables third parties to check the validity of the signature, without ever
having access to the private key.
Let’s take a first example: a JWT signed using a symmetric key and the HS256
algorithm. In this case, the signature is generated as follows:
The result is then encoded in base64url and added to the end of the JWT. The
server, in possession of the same secret, can recalculate this signature when
it receives the token and compare it with the one provided. If the two corre-
spond, the integrity and authenticity of the token are confirmed.
The advantage here is that only the server with the private key can generate a
valid signature, but verification can be delegated to other services via the
public key, which is particularly useful in distributed or microservice
architectures.
The positive points include, above all, interoperability: a JWT can easily be
transmitted between several applications, even if they use different technolo-
gies. Unlike a traditional session, there is no need to store server-side
state, which simplifies management. Simply checking the token’s signature au-
thenticates the user, without having to query a database or session system.
What’s more, the JWT format (JSON encoded in base64url) makes it a compact and
readable way of transporting data, often used in distributed architectures or
APIs.
But this stateless operation also has a major drawback: a JWT cannot be inval-
idated before its expiry date. If a token is ever compromised or a user needs
to be disconnected immediately, you will need to either:
•
set up a server-side blacklist (which reintroduces a form of state),
• or wait for the JWT to expire, which can cause security problems.
• and the Burp Suite extension ‘JWT Editor’ to generate or modify keys and
secrets in a test context.
This is a serious security error, but unfortunately one that is still encoun-
tered during our web penetration tests.
A look at RFC 7518, which defines the algorithms that can be used to sign a
JWT, reveals a surprising feature: the presence of an algorithm called none .
When the none algorithm is used, the JWT is not signed at all, and is there-
fore considered valid by default. This represents a major risk if the Web ap-
plication uses a library that implicitly authorises the use of this algorithm.
In such a case, an attacker can simply modify the JOSE header of the JWT, re-
place the alg field with none , and then freely alter the contents of the
This type of attack is possible because the header is interpreted even before
the signature has been verified, which opens the door to manipulation if the
application does not carry out rigorous checks on the accepted algorithms.
For example, let’s say a web application returns the following JWT when the
user ‘johndoe’ logs in:
johndoe’s JWT
Even if the application correctly verifies the JWT signature, a dangerous be-
haviour may persist: it accepts the none algorithm.
This means that if an attacker modifies the JWT header to indicate "alg":
"none" and removes the signature, the server will still accept the token as
valid.
Once this stage has been completed, the payload can be freely modified, en-
abling a malicious user to be assigned an administrator role, for example.
This type of vulnerability is therefore based on the absence of strict verifi-
cation of the algorithm used.
But this approach remains fragile: nothing prevents an attacker from attempt-
ing variants such as NonE or NoNe , which are likely to bypass poorly im-
plemented checks.
To brute force the secret used to sign this JWT, it is possible to use the
‘hashcat’ tool with lists of known JWT secrets.
In our case, the following command finds the secret used to sign our JWT:
hashcat -a 0 -m 16500
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImpvaG5kb2UiLCJlbWFpbCI6I
mRvZWpAZXhhbXBsZS5jb20iLCJpYXQiOjE3NDQwMzM0MDAsImV4cCI6MTc0NDExOTgwMH0.If5smKF
yDhdY5fPN-qQOawSREJIQxRLRslQeUYTn070 /usr/share/seclists/Passwords/scraped-
JWT-secrets.txt
The -a 0 option indicates the attack mode used by Hashcat. Here, the value
0 corresponds to a dictionary attack, i.e. the default approach. The -m
16500 option specifies the type of hash to be attacked – in this case, type
16500 corresponds to a JWT signature.
You then simply need to supply the JWT to be tested and a list of secrets to
try.
Once the search is complete and the secret has been found, you can display the
result using the --show option.
With this secret, the attacker can forge as many JWTs as he likes.
Sometimes the JWT’s public key is included directly in the header. In this
case, the key is in JWK (JSON Web Key) format and is placed in the jwk pa-
rameter of the header. A public key in JWK format is represented in the form
of a JSON object. Here is an example:
{
"kty": "RSA",
"e": "AQAB",
"kid": "09979990-6f43-4b28-acf1-a99dcb455c9f",
"n":
"u1bmuBchhoNbOuEYeyEjE_sOTng7boN7hdbcnQoNQNheSQCOwGcfZXE8vpFFdIFY6zVm8loYok6wE
Utq-JjDj2jFrj68asuJrFbvAyC4M6FJhP6Ox4K4UzUQlLBEJvmbFzU-
CfjyqV9xPR1q09Bg9Qc3qNeg7UYfXVgniFm5CkVjlpvtn1Xj6UHLWQ1NAqknYKcB13S4vNdAEyx69P
FaRVjco9PzbJefubaZ78YpRrMKEvknim1bH1XHCj-JKb8enhgf78J4uTwG6CMvunVkY1KKbZI-
AJjnRprAYHW26_fQg5GJkD13taTGSkNtpzrji6IY4ls-3z7Zz-IPpA4iHQ"
}
However, if the server does not set up a white list of authorised public keys,
an attacker can sign the JWT with his own private key and include his public
key in the jwk parameter, associated with this private key.
This JWT is signed with the RS256 algorithm, using a private/public key pair.
The private key is known only to the server. However, in this scenario, the
server accepts the use of the jwk parameter and allows any public key to be
added. An attacker can therefore:
1. Modify the JWT (for example, change the value of the username claim from
‘johndoe’ to ‘admin’).
We can see the inclusion of the jwk parameter, which contains the attacker’s
public key in JWK format. It is also essential to add the kid parameter,
which specifies which public key the server should use to verify the signa-
ture. In this case, the value of kid corresponds to that of jwk , which
tells the server that it must use the public key included in the jwk
parameter.
This JWT will then be accepted by the server, and the attacker will be able to
access the administrator account.
jku
The public key can also be exposed on an external server. In this case, it
generally forms part of a list of public keys (called a JWK Set), where each
key is associated with a unique identifier, the kid . This list is often ac-
cessible via endpoints such as .well-known/jwks.json or /jwks.json .
Here’s an example:
JWK Set
In this example, the JWK Set contains a single public key, which is presented
in JWK format in a JSON array named keys .
To tell the server which JWK Set to use, it is possible to add the header pa-
rameter jku to the JWT, which specifies the URL of the server exposing this
JWK Set.
However, like the attack on the jwk parameter, if the server accepts the jku
parameter without whitelisting public keys or authorised domains, an attacker
can:
2. Host the public key associated with the private key they are using on
their own server.
3. Add the jku parameter to the header, with the URL of their own server
as the value.
4. Add the kid parameter to point to the identifier of the public key
hosted on its server.
The attacker can then host the public key in a JWK Set on his own server, as
shown in the previous example. He can then add the jku parameter, pointing
to the URL of his server, include the kid parameter and sign the JWT with
his own private key.
It is crucial that the kid in the JWT matches exactly that of the public key
exposed on the server, so that the server uses the correct key in the JWK Set.
Once this stage has been completed, the JWT will be accepted by the server,
allowing the attacker to gain access to the administrator account.
It is also important to note that, as it is the server that makes the request
to retrieve the public key from an external server, this opens the door to a
risk of SSRF (Server-Side Request Forgery). However, this specific exploita-
tion will not be detailed in this article.
kid
As you can see, the kid header parameter tells the server which public key
to use to verify the JWT’s signature. Regardless of the location of the public
key, the server generally looks for one that has the same kid as the one
present in the JWT. However, depending on how the server manages the value of
the kid , classic vulnerabilities such as SQL injection or path traversal can
occur.
To arbitrarily modify a JWT, the attacker can exploit the kid parameter by
directing it to a file whose contents are known, then sign the JWT with a sym-
metric key whose value corresponds to the contents of this file.
Taking the example of the previous JWT, the attacker can first use a path tra-
versal vulnerability in the kid parameter to redirect it to the /dev/null
We can see that the JWT is signed with a secret whose value is empty. It will
therefore be accepted by the server and the attacker will have an ‘admin’
role.
Algorithm Confusion
Although it may seem unlikely, a developer can sometimes assume that the algo-
rithm used will always be asymmetric, without explicitly checking.
If the server allows the user to modify the algorithm in the JWT, an attacker
can choose a symmetric algorithm and use the public key as a secret.
For the first stage, if the public key is not exposed by the server (via the
/jwks.json endpoint, for example), it can be calculated from two generated
JWTs. Tools such as ‘rsa_sign2n’ can be used for this.
As for the second stage, it is crucial to transform the public key into a
valid format, as it must be exactly identical to the one used by the server to
verify the signature.
For example, if the attacker obtains the public key in JWK format but the
server uses an X.509 PEM format to store and verify the key, the attacker will
have to convert the JWK key into X.509 PEM format before signing his JWT.
These two steps are performed by the rsa_sign2n tool. To install and use it,
here are the commands to run:
The last command calculates the public key from two generated JWTs. In addi-
tion, the tool returns one or more JWTs signed in HS256 with the calculated
public key, in X.509 PEM format, in order to test their validity.
If one of the JWTs is valid, this indicates that the Web application is vul-
nerable to an algorithm confusion attack. The next step is to modify the JWT
to achieve the desired objective and to sign it again with the public key.
With CyberChef:
• Clearly define the JWT configuration: specify the algorithm to be used and
the claims expected in the payload. This configuration must be well
documented.
• Avoid reimplementing JWT management: use libraries that are recognised and
well maintained in the programming language used. These libraries manage
the generation and verification of signatures and the handling of claims
in a secure manner.
• Respect the defined configuration: the chosen library must follow the con-
figuration initially defined to avoid any inconsistencies that could in-
troduce vulnerabilities.
• Secure the use of the jku parameter: if this parameter is used, set up
a white list of authorised domains on the server side to avoid requests to
uncontrolled sources.
• Always include the exp (Expiration Time) claim: this limits the valid-
ity of the JWT and reduces the risks in the event of compromise.
• Use separate secrets: if you use several JWTs for different purposes or
environments, make sure you sign each one with a unique secret.
Conclusion
In this article, we take a detailed look at how JSON Web Tokens (JWTs) work:
their usefulness, structure, advantages and limitations. We have also identi-
fied the most common vulnerabilities, possible exploitation techniques and
best practices for protecting against them.
If you are interested in this subject, we strongly encourage you to put these
concepts into practice through interactive labs such as those offered by the
Burp Academy. Not only are these labs of the highest quality, they are also
backed up by clear, in-depth educational content.
Related Posts
©2024 VAADATA
TOP