Understanding JSON Web Tokens and How *Not* to Implement Them

Understanding JSON Web Tokens and How Not to Implement Them

Video Walkthrough

Trying something new with this, I also have a video of me solving the challenge. Maybe some folks will find it useful to watch.


JSON Web Tokens (JWTs) are a popular method for handling authentication in web applications. They’re compact, self-contained, and can securely transmit information between parties. However, like any security mechanism, they’re only as strong as their implementation. In this post, we’ll explore what JWTs are, how they work, and then dive into a CTF challenge that demonstrates a classic example of how not to implement them.

What are JSON Web Tokens?

Before we get into the nitty-gritty, let’s break down what a JWT actually is.

A JWT is essentially a long string divided into three parts, separated by dots:


These parts are:

  1. Header
  2. Payload
  3. Signature

Each part is Base64Url encoded, which is why they look like gibberish at first glance. Let’s break them down:

The header typically contains two pieces of information: the type of token (JWT) and the hashing algorithm being used (like HMAC SHA256 or RSA).


This is where the claims are stored. Claims are statements about the user and any additional data. Common claims include things like the user ID, username, or expiration time of the token.


The signature is the secret ingredient that makes JWTs secure. It’s created by combining the encoded header, the encoded payload, and a secret key, then applying the algorithm specified in the header.

How JWTs Work (When Implemented Correctly)

  1. The user logs in with their credentials.
  2. The server verifies the credentials and generates a JWT.
  3. The server sends the JWT back to the user.
  4. The user stores the JWT (usually in local storage or a cookie).
  5. For subsequent requests, the user sends the JWT in the Authorisation header.
  6. The server verifies the JWT and, if valid, processes the request.

Sounds simple and secure, right? Well, it can be… when implemented correctly. But what happens when it’s not?…

The FormForum Challenge: A Lesson in Poor JWT Implementation

Challenge Description

After 12 months of bruteforcing on lewis107’s account, we finally cracked his password, it was: ‘password’. See if you can get into any other accounts ;)

Connection Info: {IP:Port}

The Solve

Alright, let’s dive into this challenge! When we’re presented with a web challenge like this, it’s always a good idea to start with some basic reconnaissance. Here’s how I would approach it:

  1. First things first, let’s check the source code. You’d be surprised how often this yields results - sometimes the flag is right there in a comment! In this case, no such luck, but it’s always worth a shot.

  2. Next up, let’s try that login we’ve been given. The challenge description gives us a username (lewis107) and a password (‘password’). Simple enough, right?

  3. Before we log in though, it’s worth checking out a couple of other quick checks…

    • robots.txt: This can sometimes reveal hidden directories or files.
    • Sitemaps: These give you an idea of the site structure.
  4. Another handy tool is the browser’s Developer Tools/Inspect Element feature. This gives us access to the console, sources, and application tabs - all of which can be goldmines for information.

Alright, let’s log in with those credentials we were given. We first notice that nothing much seems to have changed on the page. But we do see the username at the top and we’re still logged in even after refreshing. How does the server know it’s still us?

This is where we start thinking about session management. In modern web applications, this is often handled by tokens. Let’s check our browser’s storage:

  1. Open the Application tab in the developer tools.
  2. Look under the Storage section for Cookies or Local Storage.

Aha! We’ve got a new token. It’s a long string of seemingly random characters. What could this be? ;)

A great tool for this kind of thing is CyberChef. When we paste our token in, it immediately identifies it as a JSON Web Token (JWT). Now we’re getting somewhere!

The token contains some data - specifically, the username ’lewis107’. So how can we modify this to access a different account?

Well, JWTs are signed with a secret key to prevent tampering. We can’t just change the username and expect it to work. We need to figure out how to sign our own token.

Let’s take a closer look at the structure of our JWT. There are some great online tools for this, like Here’s what we can see:

  1. The header specifies the algorithm (HS256) and token type (JWT).
  2. The payload contains our username.
  3. The signature… well, we don’t know the secret used to create this.

At this point, we might be stuck. We can’t create a valid token without knowing the secret. But - let’s take another look at the website.

There’s a comment from a user named ‘admin’ about handling authentication. Interesting! They mention using JWT with HS256 (which matches our token), and something about how they using the current day to keep it dynamic.

Could it be that simple? Let’s try using the current day as our secret key.

We can use an online JWT tool to create a new token:

  1. Set the algorithm to HS256.
  2. In the payload, change the username to ‘admin’.
  3. For the secret, use the current day of the month.

Now, let’s take our newly created token and replace the old one in our browser’s storage. Refresh the page and…

Boom! We’re in as admin, and there’s our flag!

Other Approach - Burp Suite

While we solved this challenge without it, it’s worth mentioning a powerful tool that’s incredibly useful for CTFs and real-world web application testing: Burp Suite.

Burp Suite is a “comprehensive platform for web application security testing”. It acts as a proxy between your browser and the web server, allowing you to intercept, inspect, and modify the requests and responses.

Here’s how we could have used Burp Suite for this challenge:

  1. Intercepting the Login Request: We could have used Burp’s Intercept feature to capture the login request. This would have shown us exactly what data is being sent to the server, including any hidden fields or headers.

  2. Analysing the Response: After logging in, we could have examined the server’s response in detail. This is where we would have first spotted the JWT being sent back to the client.

  3. Repeating and Modifying Requests: Burp’s Repeater tool would have allowed us to resend the login request multiple times with different parameters. We could have experimented with different usernames or manipulated the JWT without needing to use the browser tools.

  4. Scanning for Vulnerabilities: While not necessary for this challenge, Burp’s Scanner can automatically detect certain types of vulnerabilities, including some related to JWT implementation.

Using Burp Suite, our workflow might have looked like this:

  1. Intercept the login request and response.
  2. Identify the JWT in the response.
  3. Decode the JWT to understand its structure (Burp has a built-in JWT editor).
  4. Use Burp’s Repeater to send requests with modified JWTs, trying different secret keys.
  5. Once we found the correct secret (the current day), use Burp to send a request with a JWT for the ‘admin’ user.

While we didn’t need Burp for this particular challenge, it’s an invaluable tool for more complex web challenges and real-world web application testing. It gives you a level of control and insight that’s hard to achieve with just a web browser.

Key Takeaways

  1. Always start with basic reconnaissance - check source code, robots.txt, and use browser developer tools.
  2. Pay attention to how the application handles sessions. In this case, it was using JWTs.
  3. Understand the structure of JWTs - they consist of a header, payload, and signature.
  4. Look for clues in the application itself. The comment about authentication was the key to solving this challenge.
  5. When dealing with time-based secrets, consider all aspects - in this case, it was just the day of the month.

Remember, in real-world applications, using such a simple secret for JWT signing would be a massive security risk. Always use strong, randomly generated secrets in production!

This challenge is a great example of how sometimes, the solution is hiding in plain sight. Always read everything on the page - you never know what might be important!

Interested in seeing some more CTF challenge write ups? Click here.