Welcome back, folks!
In this post, I’ll walk you through a recent real-world engagement where I exploited a stored XSS vulnerability to gain admin access, even with cookie enabled
protection by HTTPOnly
flag. I’ll also demonstrate how I able to bypass this security measure to take over the administrative account.
Stay Tuned!
A disclaimer before we begin: The attack scenario was conducted with the formal approval of the customer.
Let’s start with a brief explanation of what HTTPOnly
protection is and how it helps developers prevent attackers from stealing account cookies.
While we’re talking about Cookies, this mechanism is what authenticates a user and distinguishes each one based on their privileges in a web application. Cookies come with various security measures to enhance their safe usage and protect the end user.
Some of the available Cookies attributes are:
setcookie entry on PHP.net documentation
HTTPOnly
explanation by PHP.net:
When true the cookie will be made accessible only through the HTTP protocol. This means that the cookie won't be accessible by scripting languages, such as JavaScript.
Oh, no!
What does it mean when, despite our injected XSS payload being part of the HTML DOM, we still can’t access the cookie value to impersonate the victim for an account takeover.
We need to explore alternative pathways to exploit our XSS injection point:
After a basic explanation of what HTTPOnly
is, let’s dive deeper into our attack flow:
Our engagement begins with a client who owns a social media platform featuring user-generated posts and a comments section for interactions from other users.
The comments page of the application looks something like this:
The first thing that immediately comes to my mind is injecting a test XSS payload to see if it’s reflected back (either temporarily or persistently) to the frontend of the application.
Unfortunately, this was not the case here due to CloudFlare protection implemented by the admin during the application deployment:
CloudFlare was recognized by its known HTTP headers
This will require us to work much harder to bypass this protection and inject our malicious XSS payload.
We can either try to peel off the CloudFlare Proxy protection and expose the underlying IP address of the web server using some Recon techniques, or find an HTML / JavaScript payload that will ‘fool’ CloudFlare into marking our payload as valid to be running on the frontend.
This time, I chose to go with the latter option of the two.
Spoiler Alert: It’s going to hurt…
After several attempts and trying to inject classic XSS payloads from the xss-payload-list, all of the XSS payloads I inserted were filtered out by the CloudFlare proxy and marked as non-executable.
At this point, I realized I needed to try harder and find a dedicated payload specifically designed to bypass CloudFlare protection.
Finally, I came up with a working payload that was reflected back into the DOM of the HTML, which was:
An hidden UI payload using display none css attribute
And we received a callback request to our temporary webhook from the XSS!
Note: it’s important to mention that we’re dealing with a Stored XSS, as the comments are saved in the application database and are extracted every time a user visits the relevant post’s comment section.
Let’s continue on..
At this point, we have a verified injection point, but we don’t want to stop here. We want to find a way to fully exploit the potential impact of this vulnerability.
Stealing the administrator’s cookie value is out (remember the HTTPOnly
flag in place?)
So, I came to the realization that we could manipulate the HTML DOM directly instead.
Getting back to the Recon stage..
During the initial stage of discovering the web application, my Wappalyzer Chrome plugin identified that the web app was written in Python:
In the market today, there are a few popular Python frameworks for web development, such as Django and Flask.
Digging into the HTML source code reveals the use of the Django framework in this case:
Javascript source code exposed Django technology
This framework is typically managed through the /admin
path, with a page titled ‘Django Administration’:
Of course at this point I didn’t have any administrative privileges and got rejected by a message of unauthorized access when attempting to access the page using my already logged-on cookie:
Hold your breath… the best part is yet to come..
So, a brilliant idea came into my mind! We can send behind-the-scenes XHR/AJAX requests to the /admin
path on behalf of the authenticated user (one with admin privileges of course).
This would allow us to steal the HTML content of the Django Admin page, extract the CSRF nonce token, and then send GET/POST requests.
Since we’re operating within the same domain origin, no any Same-origin policy violations occur in this scenario for the returned response content, enabling us to modify, delete, and alter client data, including resetting their personal passwords 😈
And the jackpot is that no phishing campaign or any account referrals to my XSS page were needed since the comment page is publicly accessible to everyone!
The final payload attack used a double JavaScript fetch
functions call to first retrieve the /admin
page contents, and then send the response back to my webhook instance:
Bingo!
The administrative Django content was captured under the Raw Content
section as below:
And finally, the rendered HTML page of the crown jewels 🥳
Conclusion
This article demonstrated some unconventional uses of a simple XSS attack, even with strong protections like HTTPOnly
in place.
Never underestimate a simple vulnerability like XSS, even in today’s fast-paced, advanced technology-driven webapps. Your imagination can take you to a new heights - use it wisely!
Thanks for reading!
Disclaimer: This material is for informational purposes only, and should not be construed as legal advice or opinion. For actual legal advice, you should consult with professional legal services.