Description
You have been hired by the Intergalactic Ministry of Spies to retrieve a powerful relic that is believed to be hidden within the small paddle shop, by the river. You must hack into the paddle shop’s system to obtain information on the relic’s location. Your ultimate challenge is to shut down the parasitic alien vessels and save humanity from certain destruction by retrieving the relic hidden within the Didactic Octo Paddles shop.
Write-Up
When we start the docker and go to the IP provided by the challenge, we arrive directly on a login page.
The source code is provided so the first reflex is to look at the different routes.
|
|
You can see that some routes are protected by AuthMiddleware or AdminMiddleware.
The AuthMiddleware will only check that your JWT session cookie is valid (so you are authenticated) while the AdminMiddleware checks that you are admin.
AuthMiddleware:
|
|
AdminMiddleware:
|
|
We realise that we will probably have to access the admin account to succeed in the challenge and therefore bypass the Admin middleware. Let’s analyze it.
First, we check that the session cookie is present. If not, we redirect to the login page.
Then, we decode the JWT token to get the algorithm used to sign it. If the algorithm is “none”, we redirect to the login page. If the algorithm is “HS256”, we verify the token with the tokenKey (JWT secret) and if the token is valid, we check that the user is admin.
Finally, if the algorithm is neither “HS256” nor “None”, we verify the token without a key. If the token is valid, we check that the user is admin. But the algorithm must be correct according to the JWT standard because the code will use this algorithm to verify the token.
In our case, we don’t have access to the secret JWT (tokenKey) so we must succeed in getting into the else
of the function to be able to validate our JWT Token without a key.
Before starting anything else, let’s analze a JWT Token that we get after register and login. I will use jwt_tool.
|
|
We can see that the token is signed with the HS256 algorithm and that the token is valid for 1 hour. We can also see that I have the id of the user (2) and that the admin user has probably the id 1.
In order to bypass the AdminMiddleware, we will have to create a JWT Token with the id 1. In order to get into the else
of the function, we will have to use a different algorithm than “HS256” or “none”.
My idea : modify the header of the token to use a “nOnE” algorithm. With that algorithm, we will not go into the first two checks of the function because "nOnE" != "none"
and "nOnE" != "HS256"
. Then, we will go into the else
and we will be able to validate our token without a key and with the JWT algorithm “nOnE” (which is a valid algorithm due to the fact that the JWT library probably has to pass all values in lowercase before processing them).
Let’s try to create a JWT Token with the id 1 and the algorithm “nOnE”.
|
|
When the algorith is “none”, JWT does not care about the signature so we can delete it.
Final token : eyJhbG{...}.eyJpZC{...}.
With this token, we can bypass the AdminMiddleware and access the admin page.
We get a simple page which simply displays the list of registered users. We can directly think about an SSTI because names are rendered in the page. The backend is in NodeJS so we have to find a NodeJS SSTI payload. I found this one.
{{:"pwnd".toString.constructor.call({},"return global.process.mainModule.constructor._load('child_process').execSync('cat /etc/passwd').toString()")()}}
So I created a new user with this name and I got the content of the /etc/passwd file.
Then I tried to get the content of the flag file.
{{:"pwnd".toString.constructor.call({},"return global.process.mainModule.constructor._load('child_process').execSync('cat /flag.txt').toString()")()}}
Flag
HTB{Pr3_C0MP111N6_W17H0U7_P4DD13804rD1N6_5K1115}