package.json
in the source code reveals what technologies we are dealing with:puppeteer
draws my attention since puppeteer is a headless API for Chrome. So, we likely will have to manipulate a Chrome browser.views/notes.ejs
we see that the <%-
tag is used, which "outputs the unescaped value into the template" according to the EJS docs under the "Tags" heading. This means we can injection HTML code into the /notes
page. Creating a new note with <script>alert(1)</script>
in the body and going to /notes
displays the alert box, which confirms our suspicion. This is probably what the first hint was referring to.report.js
we can see what happens when we submit a URL using the report function. puppeteer
is used to create an account on the website using a random username and password that cannot be bruteforced. Then, the browser navigates to the new node page, creates a note with the flag as the contents, and navigates to about:blank
, which is just a bank page. Then, 7.5 seconds later, the browser is closed.a:a
.pico
and sends it to our webhook.pico
to the /notes
page containing the flag. Our page/script will then login to our attacker account and go to the /notes
page, triggering our XSS payload (which will read the /notes
page containing the flag and send it to our webhook).window.open
. According to the MDN documentation, we can use the syntax open(url, target)
, where url
is the URL we want to open and target
is the name of the window that the resource is being loaded into. In a normal browser, using window.open
prompts the user if they want to allow a popup window to open, but in headless Chrome, window.open
will work without interaction, which is probably what hint 3 refers to.window.open
is a WindowProxy object. MDN states that "the returned reference can be used to access properties and methods of the new window as long as it complies with Same-origin policy security requirements."run_xss
as a parameter in the URL, then the window will be redirected (which is just a simple way to cause a GET request) to our webhook with the text content of the pico
window as a parameter. We pass an empty string to the url
parameter of window.open
so that a new page is not loaded and we can access the currently loaded object.data:text/html
data url. This is probably what the second hint was referring to since we are not using HTTP(S). Below is the payload:_blank
so that it opens in a new window and does not stop our script from running (by opening in the current window). Additionally, the form has the username and password fields already filled out so nothing needs to be inputted. We use a form on a whole new page instead of making the POST request using JavaScript (using the fetch
api) so that the login cookie is set.pico
(so we can refer to it using our XSS payload) and open the /notes
page with the flag on it.about:blank
until now) to the /notes
page with our run_xss
parameter, causing our XSS to run, which will grab the text content from the pico
window containing the flag and send it to our webhook.a:a
account with the second step's XSS payload. Then, paste step 3's payload into the report function and click "Report". In a few seconds you should see the flag appear in the webhook requests panel.picoCTF{p00rth0s_parl1ment_0f_p3p3gas_386f0184}