A picoCTF web challenge where the admin door was locked, but the key was hiding in a weak JWT secret.
Today, we are doing JaWT Scratchpad by picoCTF.
This challenge is under web exploitation and was made by John Hammond.
The challenge asks us to check the admin scratchpad.
Simple request.
Very suspicious request.
The website basically says:
“You can log in as anyone except admin.”
So naturally, the first thing I tried was admin.
Because when a website tells me not to touch something, that thing immediately becomes the main character.
Opening the Website
After starting the instance and visiting the website, I got a scratchpad-style login page.
The site explained that JaWT is powered by JWT and that users can log in with any name.
But there was one rule:
You cannot use admin.
Apparently, the admin user gets a special scratchpad.
So the website was basically a medieval castle guard saying:
“You may enter under any name, traveler. Except the king’s name.”
Unfortunately for him, this is a CTF.
We are not here to respect castle paperwork.
Trying to Login as Admin
Since the website clearly said not to use admin, I tried:
admin
And the site replied:
YOU CANNOT LOGIN AS THE ADMIN! HE IS SPECIAL AND YOU ARE NOT.
Rudeeee!!!!
I came here to learn web exploitation, not get emotionally folded by a scratchpad.
So instead, I logged in as:
John
The website itself suggested John, so I accepted the invitation like a polite little attacker.
Finding the JWT Cookie
From the challenge name, the website text, and the “powered by JWT” line, it was clear this challenge was about JSON Web Tokens.
So I opened the browser developer tools and checked the cookies.
Right-click → Inspect → Storage → Cookies
There was a cookie named:
jwt
Its value looked like this:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiSm9obiJ9.K1Omo0Gk5saKwJTkkgT7PUZohD7USknEE0lmT2AYAiM
That long string was the JWT.
At first glance, it looks like keyboard soup.
But JWTs have a structure.
They are divided into three parts separated by dots:
header.payload.signature
So this token is basically:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
.
eyJ1c2VyIjoiSm9obiJ9
.
K1Omo0Gk5saKwJTkkgT7PUZohD7USknEE0lmT2AYAiM
Now the soup has sections.
Still soup, but organized soup.
What Is a JWT?
JWT stands for JSON Web Token.
A JWT is commonly used to send information between a client and a server in a compact format.
In this challenge, the website used a JWT cookie to remember who I was logged in as.
The important thing to understand is:
JWTs are usually not encrypted.
They are encoded and signed.
That means I can decode the header and payload and read them, but I should not be able to change them unless I know the signing secret.
A JWT normally has three parts:
Header
Payload
Signature
JWT Part 1: Header
The first part is the header.
Decoded, it looked like this:
{
"typ": "JWT",
"alg": "HS256"
}
The header tells us two things:
typ = This is a JWT
alg = The signing algorithm is HS256
HS256 means HMAC-SHA256.
That is important because HMAC uses a secret key to create and verify the signature.
So if I want to forge a valid token, I need the secret key.
JWT Part 2: Payload
The second part is the payload.
Decoded, it looked like this:
{
"user": "John"
}
This is the part that stores the user value.
So the website was using the JWT to remember that I was logged in as John.
The interesting part is obvious:
"user": "John"
If I could change that to:
"user": "admin"
then maybe the website would treat me as admin.
But I could not just edit the payload and paste it back.
That would break the signature.
The website would notice the token had been tampered with.
The castle guard may be rude, but he can still check if the royal seal is fake.
JWT Part 3: Signature
The third part is the signature.
The signature is used to prove that the token has not been modified.
For HS256, the signature is created using:
HMAC-SHA256(header + payload, secret)
In simple words:
Header + Payload + Secret Key = Signature
So if I change the payload from John to admin, the old signature will no longer match.
To create a valid admin token, I needed the secret key.
That became the goal:
Crack the JWT secret
Modify the payload
Re-sign the token
Replace the cookie
Access the admin scratchpad
Cracking the JWT Secret with John
I saved the full JWT into a file called:
jwt.txt
Then I used John the Ripper with the rockyou.txt wordlist:
john jwt.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=HMAC-SHA256
Breaking the command down:
john
Runs John the Ripper.
jwt.txt
This is the file containing the JWT.
--wordlist=/usr/share/wordlists/rockyou.txt
Tells John to try passwords from the rockyou.txt wordlist.
--format=HMAC-SHA256
Tells John the token is signed using HMAC-SHA256, which matches the JWT header:
"alg": "HS256"
This does not decrypt the JWT.
The JWT payload was already readable.
What John is doing is trying to find the secret key that was used to sign the token.
After running John, it found the secret:
ilovepico
That was the key.
Not the signature.
The secret key.
Tiny wording difference.
Huge technical difference.
Modifying the Token
Now that I had the secret key, I could create a valid token for admin.
I went to:
https://jwt.io/
Then I pasted the original JWT.
The decoded payload showed:
{
"user": "John"
}
I changed it to:
{
"user": "admin"
}
Then I entered the secret key:
ilovepico
and kept the algorithm as:
HS256
jwt.io then generated a new valid JWT.
This new token had:
user = admin
and a valid signature created with the cracked secret.
So now I had a forged admin token.
This was no longer me trying to sneak into the castle.
This was me showing up with paperwork signed by the king’s password manager from 2009.
Replacing the Cookie
Next, I went back to the website cookies:
Inspect → Storage → Cookies
Then I replaced the old jwt cookie value with the new admin JWT.
After refreshing the page, the website accepted the token.
And just like that, I got access to the admin scratchpad.
Flag
picoCTF{jawt_was_just_what_you_thought_bbb82bd4a57564aefb32d69dafb60583}
Final Answer
picoCTF{jawt_was_just_what_you_thought_bbb82bd4a57564aefb32d69dafb60583}
Closing Thoughts
JaWT Scratchpad was a nice JWT challenge because it showed a very common web security mistake:
Using a weak signing secret.
The website did block direct login as admin, but that did not matter because the real trust was happening inside the JWT.
Once I cracked the secret key, I could create my own valid token and tell the website:
{
"user": "admin"
}
And because the signature was valid, the server trusted it.
The main lesson:
JWTs are only as strong as the secret used to sign them.
If the secret is weak enough to be cracked with a wordlist like rockyou.txt, then the whole authentication system starts looking less like security and more like a locked diary with the key taped to the cover.
Also, JWTs are not magic.
They are not automatically secure just because they look long and scary.
Sometimes the token is wearing armor.
Sometimes the armor password is:
ilovepico
And at that point, the dragon is unemployed.






