A JWT is a type of authentication token widely used to share information between client and server.
It's important to note that a JWT does not guarantee data encryption. Since JWTs are encoded, not encrypted, the JSON data you store can be seen by anyone intercepting them. Due to this, HTTPS with JWTs is highly recommended.
The most important thing to remember about JWT is that it is a signed token, not an encrypted one. Because of this, JWT cannot conceal the information contained within claims that are verified for integrity. As a result, it is not advisable to include any sensitive data in the token.
What is JWT?
In RFC 7519, a JSON Web Token (JWT) is an open standard that secures the transmission of JSON objects between entities.
Tokens (JWTs) represent claims as JSON objects encoded in a JSON Web Signature (JWS) or JSON Web Encryption (JWE) structure. Several URL-safe
parts make up a JWT (JWT claims sets), separated by the. Character. The value of each part is encoded in base64url.
A signed JWT token can verify the integrity of the claims contained within it, whereas an encrypted token hides those claims. The signature on a token signed with public/private key pairs also certifies that only the party holding the private key signed the token.
Need of Sessions
A stateless protocol, HTTP, does not remember information about the previous request. You must authenticate yourself for each request (figure 1). This sounds like a lot of work, doesn't it?
FIGURE 1
Therefore, the solution to this is the use of a session. Sessions are objects stored on the server that helps users stay logged in or store references to their accounts. This process is illustrated in Figure
FIGURE 2
In the first step, the user submits a username and password authenticated by the server. Upon successful authentication, a session ID is generated for the client. In addition to being returned to the client, the generated session ID is stored on the server.
To authenticate itself and retrieve necessary information, the client must send its session ID along with the request. After that, the server will check whether the session ID is valid. It will respond with the requested webpage/data if the session is still valid. Otherwise, the server will respond with an error message stating that the request is unauthorized.
Session-Based Authentication Problems
There are a few drawbacks to using session-based authentication. Let’s take a look at them.
Scalability: Session data must be stored on the server in memory or in a database. As APIs become more popular, servers are receiving many requests and are therefore scaling up. In addition to adding new resources, infrastructure can also become more complex.
Session Management: The server must track which sessions are active and inactive (expired or invalidated). It is necessary to remove the expired or invalidated sessions from memory.
Performance: Whenever the server receives a request, it must check if the session object provided is valid in memory. It can slow down the server if this back-and-forth continues.
How are JWTs better than sessions?
In contrast to sessions, JSON Web Tokens (JWT) do not use sessions, thereby preventing the above problems. Instead of creating a session, the server will return a JSON Web Token when the user sends credentials. Using the JWT, you can perform all authorized actions/operations.
JWTs can be compared to hotel keys: to receive your key card, you must register with the reception at the beginning of your stay. The key card can be used to open and close your room and access common amenities like the Bar, Fitness Centre, etc.
However, you are not authorized to use that key card to access any other room or manager's office. Your key card has an expiration date, so it is no longer useful after you leave the hotel.
The JWT token generated on one server can also be used on another to access resources based on the implementation. JWT tokens contain information such as expiration date and time, which can be used to confirm their validity.
When to use JWTs?
Authorization: JWT is commonly used for authorization. Upon logging in, all subsequent requests will include the JWT, allowing the user to access routes, services, and resources (based on permissions). Since JWT has a very small overhead and is easily used across different domains, it is widely used in single sign-on features.
Information Transfer: JSON Web Tokens facilitate secure information exchange between parties. The senders of JWTs can be verified by signing them, for example, by using public/private key pairs. Aside from this, since the signature is calculated based on the header and payload, you can verify that the content hasn't been altered.
Advantages of JSON Web Token
Compactness: JSON takes up less space when encoded than XML, so JWT takes up less space than SAML.
No need for Session: Because a JWT contains all the necessary information about the user, a server session object is not needed, saving memory.
Built-in Expiration: JWTs have claims that can be used to assign an expiration date/time. After the expiration period, the token can become invalid on its own.
No need for Cookies: Local Storage, indexDB, or some native store can be used to store the token. As a result, CORS and CSRF attacks will be prevented.
Compatibility: JSON parsers are popular in most programming languages because they map directly to objects. In contrast, XML does not have a natural document-to-object mapping. JWTs are, therefore, easier to use than SAML assertions.
JWT Structure
JSON Web Tokens consist of three parts separated by a period(dot).
- Header
- Payload
- Signature
Thus, a JWT usually looks like this.
<<header>>.<<payload>>.<<signature>>
Let’s understand the parts of a JWT token in detail:
Header
There are typically two parts to the header: the token type, JWT, and the signature algorithm, such as RSA or HMAC.
For example:
This JSON object is Base64 encoded to form the header (first part) of the JWT.
Payload
The second part of the token contains the information (claims) sent by the server. This can be information about an entity (typically a user) and additional data about that entity. Claim types include registered, public, and private.
Registered claims: The claims in a registered claim set are not mandatory, but they are recommended to provide a useful set of interoperable claims. Some of them are:
- nbf (not before)
- exp (expiration time)
- sub (subject)
- aud (audience)
- iat (issued at)
- others
Public claims: Developers can define these claims at their discretion. To avoid collisions, they must be defined in the IANA JSON Web Token Registry or as URIs containing collision-resistant namespaces.
Private claims: Neither registered nor the public are custom claims created to share information between parties that agree to use them.
For Example:
Signature
To create the signature, you need to take the encoded header, the encoded payload, the secret, and the algorithm specified in the header and sign that.
For example, if you use the HMAC SHA256 algorithm, a signature will be created as follows:
When a token is signed with a private key, it can verify that the sender is who they claim to be. Signatures are used to verify that a message was not changed along the way.
Combining Result
This JWT contains the previous header, payload encoded, and a secret signature. You can decode a JWT using JWT.io.
How Vulnerability Arises?
In most cases, JWT vulnerabilities are caused by incorrect JWT handling within an application. As JWT specifications are designed to be relatively flexible, website developers can pick and choose many implementation details. They may thus accidentally introduce vulnerabilities, even when using battle-hardened libraries.
In most cases, these implementation flaws result in an incorrectly verified signature of the JWT. As a result, an attacker can tamper with the values passed to the application via the token's payload. A robustly verified signature may not be trusted unless the server's secret key remains secret. Leaking, guessing, or brute-forcing the secret key could allow an attacker to generate a valid signature for any arbitrary token, compromising the entire system.
Common Vulnerabilities in JWT
Now that we have understood how JWT works, let’s take a look at some vulnerabilities that arise in JWT:
1. None Attack
There are two JSON objects in JWTs, a header and a payload, which contain important information. The header contains information about the algorithm used by the JWT to sign or encrypt its data. Encrypted JWTs encrypt only the payload, while signed JWTs sign both the header and the payload.
The header and payload of signed tokens are protected against tampering, but the data contained in the JWT can be changed without modifying the signature. What are the steps?
For instance, let's consider a JWT with certain headers and payloads. Here's what it looks like:
Imagine that you encode this token into its compact serialized form with a signature and a secret signing key. For this, we can use JWT.io. Here is the result:
Since this token is signed, we are free to read it. As a result, we could also construct a similar token with slightly changed data, although we couldn't sign it without knowing the signing key.
If attackers don't know the signing key, what could they do? Malicious users can use a token without signature in this type of attack!
In the first step, the attacker modifies the token. For example
You can try the following none algorithm variants:
- none
- None
- NONE
- nOnE
Encoded:
An attacker who successfully uses this token may be able to escalate their privileges based on the implementation.
2. Weak HMAC Keys
To produce and verify signatures, HMAC algorithms rely on a shared secret. Many people assume that shared secrets are like passwords, and in a sense, they are. However, those are the only similarities.
In contrast to other types of secrets, passwords require a relatively small minimum length, even though the length is an important property. To prevent brute force attacks hashing algorithms are used (along with a grain of salt) to store passwords to prevent brute-force attacks.
In contrast, HMAC-shared secrets used in JWTs are designed to be as fast as possible. The result is an efficient sign/verify operation but makes brute force attacks easier. Therefore, the shared secret length for HS256/384/512 is crucial. JSON Web Algorithms define that the minimum key size should be equal to the size in bits of the hash function used in combination with HMAC to avoid any brute-force attacks.
Exploiting JWT with Weak HMAC Keys:
HS256, HS384, and HS512 use symmetric encryption, meaning the key that signs the token also verifies it. We can verify signatures offline since it is self-contained. With John the Ripper, HMAC secrets can be cracked offline.
Note: We can use different brute forcing techniques as well as different wordlists here
3. JWKS Injection/JWKS Spoofing
As the name suggests, the JKU (JWK Set URL) Header Parameter is a resource that contains a set of JSON-encoded public keys, one of which is used for digitally signing the JWS.
You can see an example of a header below, along with the JWKS (JSON Web Key Store) it points to:
JKU Header
JWKS File
Token signatures will be verified using this JWKS file, so if we control the JKU header, we control the JWKS file and can verify tokens on the server.
Exploitation Steps:
Create a private key using mkjwk.org.
Host the public key in a JWKS on a web server. Host this file since it is needed to be available statically.
- Develop a malicious token containing the JKU, pointing it to your JWKS, and sign it using your private key. Use jwt.io first, to convert the public and private keys to the PEM format.
4. Disclosure of a Correct Signature
You can try changing the payload and sending such tokens to an API endpoint. It is possible to get the correct signature matching the changed data if the endpoint displays exceptions to the user.
Example:
Attacks via kid Parameter:
The kid is one of the claims in the header of the JWT token, which presents a key identifier. Let’s take a look at a few attacks via the kid parameter.
1)SQL INJECTION
A database can be used to store keys for an application. If referenced in the kid parameter, this key might be vulnerable to SQL injection.
You will find a parameter called KID when decoding the JWT or different JWT. Claims that retrieve a key file from the file system. They can be exploited if they aren't properly sanitized. Here is an example where we are changing the header, not the payload or signature. In most cases, KID retrieves a key file from a file system.
Example:
1) In place of value 1, we can modify it and add the SQLi payload. Ultimately, the JWT will appear as follows:
2) Then we will send manipulated JWT token to the server via burpsuite.
3) See response in burpsuite.
2)Path Traversal
When verifying the token, the kid parameter specifies a filesystem path to the key. An attacker can generate a forged token by entering a file path with predictable content in the kid parameter since the secret key is already known.
Linux systems use a file called /proc/sys/kernel/randomize_va_space, which has predictable values like 0,1,2. By using secret values like 0,1,2 an attacker can create a malicious token.
3)Command Injection
It is possible to pass the kid parameter to the system-like function to inject a command:
Substitution attacks
In some attacks, a JWT is intended for a recipient and is used for a recipient for it was not intended. For example, consider the following:
- Applications fail to validate (or not validate properly) that the cryptographic keys used in the cryptographic operations in the JWT belong to the issuer (the iss claim).
- In some applications, the subject value (a sub-claim) is not validated (or is not validated correctly) to ensure that it corresponds to a valid subject and/or issuer/subject pair (this may include confirming that the application trusts the issuer).
- AUD claim is not used (or is incorrectly used) to determine whether the JWT was substituted at an unintended party by the same issuer if the same issuer issues JWTs for more than one relying upon the party.
Best practices when working with JWT
- When verifying any JWT you receive, consider edge-cases like JWTs signed by unexpected algorithms and carry out robust signature verification.
- Set up a strict whitelist of hosts that can access the JKU header. Ensure that the kid header parameter does not expose you to path traversal or SQL injection.
If your developers handle JWTs, use an up-to-date library and ensure they understand how it works and any security implications.
- Always set an expiration date for any tokens that you issue.
- Avoid sending tokens in URL parameters where possible.
Detailed Best Practices
Let’s understand the best practices in much more detail:
Always Perform Algorithm Verification
This mitigation can prevent the "alg": "none" attack and the "RS256 public-key as HS256 shared secret" attack. To prevent giving attackers control over JWTs, the algorithm must be explicitly selected each time.
Always Validate Cryptographic Inputs
Some cryptographic operations are not well-defined for inputs outside their range of operation. The attacker can exploit these invalid inputs to gain access to sensitive information (such as a private key) and produce unexpected results.
Use Strong Secret Keys
Even though this recommendation applies to any cryptographic key, it remains frequently ignored. It is common to overlook the minimum length for a shared secret.
In addition to being long enough, the shared secret must also be completely random. Despite a high level of randomness (also known as "entropy"), a long key can still be brute-forced or guessed. Key-generating libraries must use pseudo-random number generators (PRNGs) that are cryptographically quality and properly seeded during initialization to prevent this from happening. Ideally, hardware number generators should be used.
Validate All Possible Claims
Some attacks rely on incorrect validation assumptions. They rely solely on signature validation or decryption for validation. By using correctly signed or encrypted tokens in unexpected contexts, attackers may gain access to correctly signed or encrypted tokens that can be used for malicious purposes.
Use The typ Claim To Separate Types Of Tokens
typ claims usually have one value (JWT), but they can also be used to separate application-specific JWTs. Your system can benefit from this if it has to handle many different types of tokens.
An additional claim check can also prevent the misuse of a token in a different context.
Conclusion
JWT is a revolutionary mechanism, and it has so many features. It is very beneficial and used widely. But if not implemented properly, then can cause very critical vulnerabilities. Hence, the design and implementation of JWTs should be flawless to avoid further loss.
References
- https://jwt.io/introduction
- https://workbook.securityboat.in/resources/web-app-pentest/jwt-and-its-bypass
- https://portswigger.net/web-security/jwt
- https://pentestbook.six2dez.com/enumeration/webservices/jwt
- https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/06-Session_Management_Testing/10-Testing_JSON_Web_Tokens