JWT-1
Challenge Info
I just made a website. Since cookies seem to be a thing of the old days, I updated my authentication! With these modern web technologies, I will never have to deal with sessions again. Come try it out at http://litctf.org:31781/.
Understanding what a JWT is
The link that I’m given for this challenge is http://litctf.org:31781/
. Before even messing with it though, I googled “JWT” to get some further context.
I found this Wikipedia page. The quick summary, however, is this:
JSON Web Token is a proposed Internet standard for creating data with optional signature and/or optional encryption whose payloads holds JSON that asserts some number of claims. The tokens are signed either using a private secret or a public/private key.
Attempts
Next, I visited the link, where I was greeted with this:
Naturally, my first response was to hit the giant button that screams “GET FLAG”. This obviously didn’t provide anything (that’d be too easy, and that’s no fun).
Then, I want back to the “Log in” page, and decided to log in with the user admin
and the password admin
, since alot of bad sites will use these as the default. This didn’t work though, and I started to just try a bunch of different combinations, but each returned the same result:
Getting there…
Finally realizing this challenge wouldn’t be THAT easy, I opened up Burp Suite to try and map out the site (maybe there’s a hidden directory!).
I couldn’t find any hidden directories though, so I altered my approach, instead of trying to map out the site, I tried intercepting through Burpsuite.
Mostly, it looked like a normal site, but one thing did catch my eye- a cookie with the value of eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiMTIzIiwiYWRtaW4iOmZhbHNlfQ.0Pi%2FH9Rz7ylX%2FM1MwPS469hjUu3b9gV0%2Fl8EW6roQC0
:
This immediately led me to think: “Can I manipulate this token to get admin?..” So, I went back to the “GET FLAG” screen, and decided to inspect element to take a look at the cookies:
For awhile, I messed with the value
field. I tried changing it to admin
, 123
, etc. Eventually, the correlation struck me- the “value” is actually a JWT (JSON Web Token)
Solution
One of the first results when googling “JWT” is a site called jwt.io. This site lets us decode and modify JWT tokens, so it’s crucial to beating the challenge:
I decided to put the token I had into the “Encoded” field, and noticed that the information in the “PAYLOAD” field reflected the login credentials I had tried earlier.
From there, I tried modifying the “admin” value from false
to true
, and noticed that the encoded field automatically updated to reflect the changes.
Then, I went back to Burpsuite intercept, and replaced the old cookie token with the NEW token (which is the same token, but with we modified admin: true
), which for me was. I hit “forward”, and got the flag.
flag: LITCTF{o0ps_forg0r_To_v3rify_1re4DV9}
JWT-2
Challenge Info
its like jwt-1 but this one is harder. URL: http://litctf.org:31777/
attached: index.ts
Preface
While this is a separate challenge, its fundamentals are heavily derived from the first JWT challenge, which I heavily recommend you read first.
Trying the first solution
Since this is a continuation of the first JWT challenge, I figured I’d try the same solution. A quick recap on how I beat the first one:
- Register an account on the given site
- Use Burpsuite intercept to capture the JWT associated with our account
- Use jwt.io to read the contents of our JWT
- Modify
admin
so that it equals totrue
- Access the “GET FLAG” button with our new JWT and acquire flag
For this challenge, while I was able to capture the JWT token and modify it to have admin
set to true
, upon actually using it, I was greeted with this:
Inspecting the attached TypeScript file
By now, I realized this challenge wouldn’t be as simple as the last one, so I decided to skim through the attached TypeScript file, which I’ll leave down below:
1 | import express from "express"; |
Just to highlight what this code does:
const [header, payloads, signature] = token.split(".");
- splits the JWT token into its three parts; header, payloads, and signature, and the
.
acts as a delimiter.
if (!req.cookies.token)
- checks if the token is missing, and responds with
403 Unauthorized
if it is.
Buffer.from(header,"base64").toString()
Buffer.from(payloads,"base64").toString()
- The header and payloads are decoded from base64 to their original string format.
However, what’s really making us hit our head is the following:
1 | const sign = (payloads: object) => { |
This removes the =
characters from both the base64-encoded payloads and signature. This is normal in JWTs though, and doesn’t affect the token’s validity. The main issue here isn’t the removal of padding, but making sure that the token we craft adheres to this format.
Solution
The payloads that I came up with:
1 |
|
This payloads creates a token using the xook
secret, modifies admin
to true
, and removes all padding (=
characters).
Then, I replaced the old token in Burpsuite intercept with the new one: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoieW91cl91c2VybmFtZSIsImFkbWluIjp0cnVlfQ.xwxnk5ogziOC8xlMNuolHBuQDbefnLA9rATCeS7fS+s
, and hit “forward”.
flag: LITCTF{v3rifyed_thI3_Tlme_1re4DV9}