Exfiltration via CSS Injection
Todayâs topic is something thatâs already pretty well covered: CSS injections. I wanted to talk about my experience implementing this attack on a real site. As you may have encountered, the situation in which you find a vulnerability may not be the pristine situation many vulnerabilities are originally described in (like XSS but with a WAF). As such, writing about the experiences researchers encounter in real life can give light to practical implementations of preventative mechanisms (or general roadblocks) and bypasses for those blockers.
As some of you may know, Iâve recently taken up bug bounties in my spare time. While doing bug bounties, Iâve had the wonderful opportunity to work with two of the best hackers in the business: Behrouz Sadeghipour and Brett Buerhaus. While we were hacking away at various targets, nahamsec (Benrouz) messaged me saying they had found a CSS injection but had trouble exploiting it.
The main use for exploiting a CSS injection is data exfiltration from input elements. The input elements weâre mostly concerned with are usually CSRF token input elements as these are commonly placed on the page as type=hidden
input elements in forms. This brings us our first problem with exploiting this CSS injection: in both Chrome and Firefox, input[type=hidden]
elements do not fetch background-image
urls.
The general CSS injection data exfil method is to use css like:
Then, attacker.com
would load an iframe with this css injection on it on target.com
. attacker.com
would then wait for a request to https://attacker.com/exfil/<data>
.
Letâs assume the token is csrF
. This CSS would trigger a page load on https://attacker.com/exfil/c
. Then, we would reload the iframe but with CSS like:
Which would cause a page load at https://attacker.com/exfil/cs
. Eventually, after repeating this pattern a few times, the final request to https://attacker.com/exfil/csrF
which allows an attacker to learn of the visitorâs CSRF token.
With the specific site that we were looking at, there was, thankfully, no X-Frame-Options
specified, which would prevent us from using iframes
to exploit the css
injection in a simple way (you could technically open more tabs.. but thatâs really ugly.. and super ânoisyâ).
The Problem
Ok, so thatâs all explained and seems simple enough to implement, whatâs the problem? Well, remember that type=hidden
input elements donât actually request those images? It turns out that since almost all csrf tokens in forms are type=hidden
which means that it makes directly using the input field to exfiltrate data much harder.
Originally, we looked at other methods we could use to call out from the page but we didnât find anything simple. We figured that there was another way to do this attack but we just had to figure it out. After some investigating, we learned of ~
and +
in CSS which are the general and adjacent sibling selectors.
What the sibling selectors let you do is take an element that shares a common parent and style it based on a CSS query. For example, imagine we had a page like the following:
With the following CSS:
This says (read right to left) âAny p
thatâs a sibling to a p[color=red]
should have color: red
.
What this selector lets us do is say âif any element is next to an input element that contains the CSRF token, set a background-image itâ. We can construct a potential CSS directive by similarly writing right to left on the statement above:
Since there existed a form on our target with elements that were not type=hidden
, we were able to cause those elements to make the request on behalf of the CSRF token element.
Lastly, hereâs an anonymized version of the PoC that we used for our report: https://gist.github.com/d0nutptr/928301bde1d2aa761d1632628ee8f24e
This doesnât include the server side code as most of that should be pretty obvious (we had the background-image
requests set a value on a cookie; this also has the benefit that our PoC wonât work in Safari).
The Future
A couple of the forms on the page were concerning us when we were originally attempting to exploit this vulnerability. Some of the forms only contained hidden inputs. This would mean that we couldnât use the sibling selector technique because none of the siblings would be able to make a request via background-image
for us.
Some research into this problem yielded the following proposed directive in the selectors-4
draft from the W3C. :has()
allows you to perform a similar query to the sibling selector with the big difference being that you can make this query from the parent of an element.
For example, in our proof of concept, one could instead have written:
Which says âany form that has a descendant input element with name csrf
and value token
should have a background-image of <url>â.
This, obviously, would work when all input fields are type=hidden
and allow for easier general exploitation of this issue. Currently, this feature hasnât made it into general support (or any support, for that matter) so it might be some time before we can use this for CSS injection exploitation. Itâs definitely something to look out for, though!
Closing Remarks
Thatâs it for this monthâs article. The next article will be either about an event Iâm about to participate in at the start of August, or a phishing trick that I (re?)discovered via the macOS Mail Client.
Speaking of events and August, Iâll be in Vegas from August 6th to 13th. Iâll be around at various events during then but if youâre interested, Iâd love to meet up :)
See ya next time!