Didactic Octo Paddles is a web challenge that features an application with a vulnerable implementation of JWT token validation using the None
algorithm. This can be leveraged to forge a JWT that impersonates the admin
user which then allows an SSTI vulnerability to be exploited in the /admin
route, leading to LFI and discovery of the flag.
Initial visit to the app showed a login page:
/routes/index.js
contained a /register
route:
I registered a new user, mike
:
After logging in, the page was a basic e-commerce site that allows users to add items to their cart and view their cart, but not much beyond that in terms of functionality.
/routes/index.js
also had an admin
route:
So, I took the cookie from my current session as the user mike
and used Burp Suite to try and access /admin
which responded with a 403
error and the message: You are not an admin
I went to JWT.IO to view the details of my token:
JWTs are usually encoded using Base64 and can consist of either two or three parts, depending on the algorithm used. The token typically consists of a header, a payload, and a signature. The header specifies the algorithm type (e.g., HS256) and the token type (JWT). The payload contains the user ID, the issued at timestamp, and expiration time. Any algorithm besides the None
algorithm has a third part which is the signature. The signature is used to verify the authenticity and integrity of the JWT.
Further analysis of the code showed an insecure implementation of validating the alg
parameter in the header. Since the code in AdminMiddleware.js
validates the alg
parameter by attempting to filter out strings that contain "none"
, it can be bypassed by simply passing in "None"
as the value for the "alg"
parameter because it's case sensitive.
database.js
revealed that the only other user in the database was admin
, so it could be assumed that the id
of this user was 1
:
To create a JWT that will use the None
algorithm and impersonate the admin
user by setting the id
parameter to 1
, I needed to create two parts (JWTs using the None
algorithm don't require signatures):
-
The first part is the header which will specify the
None
algorithm and token type ofJWT
-
The second part is the payload and will set the
id
to1
, and use the sameiat
andexp
of my previous JWT since those are valid values on the server.
I ran the following commands to generate both parts of the new token:
The generated token details (note that it uses the None
algorithm and has an id
of 1
):
Then, I tried accessing the admin
route with the forged JWT and received a 200
response which brought up the admin dashboard page:
The response body showed the two users in the database: admin
and mike
The code shown below in the /admin
route uses the jsrender
template engine, and it's implemented in a way that directly injects usernames into the HTML without properly sanitizing the input. This makes it vulnerable to server-side template injection which could be leveraged to get local file inclusion.
admin.jsrender
injects the username data into the HTML which is then displayed on the /admin
dashboard page:
So, I sent the following payload from HackTricks which registers a new user by injecting JavaScript code within the username field that executes a command to retrieve the contents of the /etc/passwd
file. This can then be viewed by visiting the /admin
page.
{"username":"{{:\"pwnd\".toString.constructor.call({},\"return global.process.mainModule.constructor._load('child_process').execSync('cat /etc/passwd').toString()\")()}}","password":"test"}
Next, I visited the /admin
route to reveal the contents of /etc/passwd
:
The flag was located in /flag.txt
:
So, I sent the same payload as before to the /register
route, but changed cat /etc/passwd
to cat /flag.txt
:
Finally, I visited the /admin
route to get the flag: