Exploiting JWTs (JSON web tokens)
What is a JWT (JSON Web Token)
JSON Web Tokens (JWTs) are a compact, standardized format for securely transmitting JSON data between systems. While they can represent various types of information, JWTs are most commonly used to carry claims about a user such as identity, roles, and access permissions for authentication and authorization workflows.
What sets JWTs apart from traditional session tokens is their self-contained, stateless design. Instead of storing session data on the server, all relevant information is embedded directly within the token and stored on the client. This makes JWTs ideal for scalable, distributed systems where users may interact with multiple backend services without the need for centralized session storage.
JWT VS Session Cookies
JWTs and session cookies are both used in authentication, but they work differently. JWTs are self-contained tokens that carry user data directly in the token and are typically sent in the Authorization header. This makes them ideal for stateless, scalable systems like micro services or APIs. Session cookies, on the other hand, usually store a session ID that maps to server-side data and are sent automatically by the browser.
The key advantage of JWTs is that they don’t require server-side session storage. They’re easy to use across services, carry all the necessary claims (like user ID and roles), and come with built-in expiration handling.
JWT Formatting
A JWT is made up of three parts that are Base64URL-encoded:
- Header – Contains metadata about the token, like the type (
JWT
) and the signing algorithm used (e.g.,HS256
). - Payload – Holds the actual data or “claims,” such as the user’s ID, role, or token expiration time. This is the part that carries the information being transferred.
- Signature – A cryptographic signature created using the header, payload, and a secret key. It’s used to verify the token’s integrity and ensure it hasn’t been tampered with.
Each part is base64url-encoded and combined into a single string like this:
header.payload.signature
Header
Tells the server how the JWT is signed and what type of token it is.
{ "alg": "HS256", "typ": "JWT" }
Base64 Encoded Output
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Payload
Contains information about the user or session.
{ "sub": "1234567890", "name": "John Doe", "role": "admin", "iat": 1713050123 }
Base64 Encoded Output
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzEzMDUwMTIzfQ
Signature
The server that creates the token usually generates the signature by hashing the header and payload together using a secret key.
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
Base64 Encoded Output
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Putting it all together.
Each part is base64url-encoded and separated by dots. You can decode JWTs using tools like jwt.io to view these parts in a human-readable format.
JWS or JWE?
When working with JSON Web Tokens (JWTs), you’ll often come across terms like JWS and JWE. While they’re closely related, each serves a specific role within the JWT ecosystem. To put it simply JWT is the token format while JWS and JWE are two different ways that token can be secured; one by signing it, the other by encrypting it.
A JWS (JSON Web Signature) is a JWT that has been digitally signed. This signature ensures that the contents of the token haven’t been tampered with and that it was issued by a trusted source. However, the data inside a JWS remains unencrypted and it can be decoded and read by anyone who has access to the token. This makes JWS ideal for situations where you need to verify the integrity and authenticity of the data, but don’t mind if the payload is visible, such as validating an OAuth access token.
On the other hand, a JWE (JSON Web Encryption) is a JWT that has been encrypted to ensure confidentiality. Unlike JWS, the contents of a JWE are completely hidden and can only be read by the intended recipient who has the proper decryption key. This makes JWE the better choice when you need to transmit sensitive data, such as personal user information, and want to make sure it stays private during transit.
Common Security Risks and Recommendations
One of the biggest risks is using a weak or guessable secret key. Since the JWT signature is created using this key, a short or predictable value (like “secret” or “admin”) can be brute-forced, allowing an attacker to forge valid tokens. To prevent this always use strong, randomly generated secrets and store them securely using environment variables or a secret manager.
Another well-known issue is the “none” algorithm vulnerability. Some JWT libraries have historically allowed tokens to be created without a signature by setting the algorithm to “none”. If the server doesn’t explicitly block this, an attacker could craft an unsigned token that the server mistakenly trusts. The fix? Always enforce strict algorithm validation and never allow “none” or unsupported algorithms.
Token expiration is another important piece of the puzzle. JWTs are often valid for a specific time period, but if developers forget to include an exp (expiration) claim, the token could be valid indefinitely. To reduce this risk, always include an exp field, and consider short expiration times along with refresh tokens for extended sessions. For added control, you can also use iat (issued at) and nbf (not before) claims to narrow down the valid time frame.
Attacking JWTs with PortSwigger Academy Lab:
Let’s take what we have learned and solve the PortSwigger Academy lab JWT authentication bypass via unverified signature. We will use Burp Suite and the extension JWT Editor to solve the lab.
Clicking the Access the Lab button will redirect us to the lab environment.
Using the credentials provided to us (wiener:peter) we can authenticate into the application and view our account information.
If you do not have the JWT Editor extension installed, you will need to do so under the Extensions tab. At the time of writing this post the JWT Editor extension can be used for Burp Suite Community Edition and Pro Edition.
Navigating back to the Proxy tab we should see green highlighted Request. This is JWT Editor letting us know a JWT is in this HTTP Request.
Sending the my-account?id=wiener Request to Repeater will allow us to tamper with the JWT by clicking on the JSON Web Token tab.
From top to bottom we have the JWS Header, Payload, and Signature. The amount of options we have from this extension is incredible. This extension can be used to solve several of the PortSwigger labs, but let’s keep focus and solve this lab first. Other write ups will cover other components of this extension.
Our goal for this lab is to get administrative access to the /admin panel. Let’s simply update the “sub” from wiener to administrator in the payload and see if this allows us to bypass authentication due to not verifying the signature of the JWT.
After updating the “sub” claim to administrator we can try and access /admin due to the server not verifying the signature of the JWT.
Here we can see we have access to the /admin panel and can delete the users due to the server not verifying the signature of the JWT we provided.
It appears we can delete the user Carlos and solve the lab by navigating to /admin/delete?username=carlos with our updated JWT.
After deleting the user Carlos with our updated JWT we have successfully solved the lab!
Congratulations on solving this lab and exploiting this JWT!
This concludes the write up on Exploiting JWTs . I hope you found value in this content.
References and Other Tools:
https://portswigger.net/web-security/jwt