This write-up presents a possible approach for solving the SantaCloud CTF challenge by Intigriti.
If you prefer to save time and have a quick summary of the proposed solution, feel free to skip ahead to Steps to capture the flag.
The challenge rules state that the solution should require no bruteforcing. This suggests that aggressive fuzzing or blind guessing are not needed. The tip published later by Intigriti confirms this line of reasoning.
We start by examining the main page of the challenge server at https://santacloud.intigriti.io/home. Nothing more than a landing page that allows users to sign in to the portal.
By inspecting the HTML code of /home we notice that, after a successful login, the user is redirected to /dashboard. We can try to directly access the dashboard: indeed, the page is briefly rendered before redirecting the user to the login form.
The behavior observed for the /dashboard endpoint turns out to be consistent throughout the portal, meaning that we can map the application and inspect the HTML code of every page without being authenticated.
We end up with the following site map:
/home/login/dashboard- Dashboard Overview/map- Global Delivery Map/inventory- Inventory Management/profile- User Profile/notes- Internal Notes
In addition, we notice that the application performs some API requests, such as:
GET /api/giftsGET /api/notesGET /api/notes/{noteId}POST /api/notesPUT /api/notes/{noteId}DELETE /api/notes/{noteId}
Unfortunately, all these APIs are protected by a Bearer token issued upon login, and any attempt to call them results in the following error message: "Invalid or expired token".
Finally, the HTML code of /map reveals that the /api/gifts endpoint accepts user_id=1 as a query parameter. Maybe something that could be of use later on.
...
// Try to fetch all users' gifts for better map visualization
// This will work if there's an endpoint or parameter
const allResponse = await fetch('/api/gifts?user_id=1', {
...
Another valuable reconnaissance step is reviewing web server metadata files for potential information leakage. With this in mind, we inspect the content of the /robots.txt file.
User-agent: *
Allow: /
# Disallow indexing of sensitive config files
Disallow: /package.json
Disallow: /backup.json
Disallow: /artisan
Disallow: /.env
Disallow: /.env.local
Disallow: /composer.json
Disallow: /composer.json*
Disallow: /composer.json~
Out of the listed "sensitive config files", only /composer.json~ turns out to be accessible.
Interestingly, this file contains the credential of the elf_supervisor user and... the flag!
Wait... remember the challenge rules: we must find a flag in the format INTIGRITI{.*}. So, that secret contains only a part of the flag.
We need to dig further...
We sign in as the elf_supervisor user and start exploring the application, playing around with all the available functionalities. Unfortunately, no trace of the flag.
The "User Profile" page tells us that our "User ID" is 2.
Something rings a bell and we review the notes taken during the initial reconnaissance: the /api/gifts endpoint accepts user_id=1 as a query parameter, so the identifier of another user. Performing such an API request effectively returns the gifts of the admin user. However, the gift we were hoping to receive is not among them.
Most importantly, this indicates a lack of proper authorization checks. Maybe other APIs are affected by the same issue.
Indeed, this is the case for the /api/notes endpoint. This API is called by the application while loading the "Internal Notes" page from the user profile. By intercepting this request and adding user_id=1 as a query parameter, we can access the private notes of the admin user.
And here it is! the "Secret Key" note contains the flag we need to solve this challenge: INTIGRITI{019b118e-e563-7348-a377-c1e5f944bb46}.
This challenge was about identifying exposed information through careful reconnaissance, highlighting how valuable this phase is. By inspecting the content of the /robots.txt file, we were ultimately able to obtain a valid user credential for the portal. In addition, HTML code review gave us clues that allowed to identify and exploit an authorization flaw to retrieve the flag.
- Inspect the content of the
/robots.txtfile. Focus on the lineDisallow: /composer.json~. - Use the credential exposed within the
/composer.json~file to sign in as theelf_supervisoruser. - Navigate to
/noteswith Burp Intercept on. - Intercept the request to the
/api/notesendpoint and adduser_id=1as a query parameter. - The "Secret Key" note contains the flag:
INTIGRITI{019b118e-e563-7348-a377-c1e5f944bb46}.