https://portswigger.net/research/ublock-i-exfiltrate-exploiting-ad-blockers-with-css
Buy a Burp Suite Certified Practitioner exam, pass before 15 Dec, and
we'll refund your $99. - Find out more
Login
[ ] ( )
( ) ( ) ( ) ( ) ( ) Products Solutions Research Academy Daily Swig
Support Company
Customers About Blog Careers Legal Contact Resellers
My account Customers About Blog Careers Legal Contact Resellers
Burp Suite Enterprise Edition Burp Suite Enterprise Edition The
enterprise-enabled web vulnerability scanner. Burp Suite Professional
Burp Suite Professional The world's #1 web penetration testing
toolkit. Burp Suite Community Edition Burp Suite Community Edition
The best manual tools to start web security testing. View all product
editions
Get Burp Suite Certified
Prove your skills with the world's leading pentesting toolkit.
Burp Suite Certified Practitioner
Application Security Testing See how our software enables the world
to secure the web. DevSecOps Catch critical bugs; ship more secure
software, more quickly. Penetration Testing Accelerate penetration
testing - find more bugs, more quickly. Automated Scanning Scale
dynamic scanning. Reduce risk. Save time/money. Bug Bounty Hunting
Level up your hacking and earn more bug bounties. Compliance Enhance
security monitoring to comply with confidence.
View all solutions
Get Burp Suite Certified
Prove your skills with the world's leading pentesting toolkit.
Burp Suite Certified Practitioner
Support Center Get help and advice from our experts on all things
Burp. Documentation Browse full documentation for all Burp Suite
products. Get Started - Professional Get started with Burp Suite
Professional. Get Started - Enterprise Get started with Burp Suite
Enterprise Edition. Releases See the latest Burp Suite features and
innovations. User Forum Get your questions answered in the User
Forum.
Visit the Support Center
Get Burp Suite Certified
Prove your skills with the world's leading pentesting toolkit.
Burp Suite Certified Practitioner
[ ] Articles
* Overview
* [ ] Core Topics
XSS Request Smuggling Template Injection Top 10 Hacking
Techniques
* Articles
* [ ] Meet the Researchers
James Kettle Gareth Heyes Michael Stepankin
* Talks
uBlock, I exfiltrate: exploiting ad blockers with CSS
[ ]
*
*
*
*
*
*
Gareth Heyes
Gareth Heyes
Researcher
@garethheyes
* Published: 06 December 2021 at 14:00 UTC
* Updated: 06 December 2021 at 14:25 UTC
*
Image showing code being extracted from a vacuum cleaner
Ad blockers like uBlock Origin are extremely popular, and typically
have access to every page a user visits. Behind the scenes, they're
powered by community-provided filter lists - CSS selectors that
dictate which elements to block. These lists are not entirely
trusted, so they're constrained to prevent malicious rules from
stealing user data.
In this post, we'll show you how we were able to bypass these
restrictions in uBlock Origin, use a novel CSS-based exploitation
technique to extract data from scripts and attributes, and even steal
passwords from Microsoft Edge. All vulnerabilities discussed in this
post have been reported to uBlock Origin and patched.
Please note that these techniques assume a malicious rule has been
installed. We did find a technique to encourage malicious rule
installation, but believe that the most plausible attack vector is a
compromised filter list. Due to ethical (not to mention legal)
concerns, we opted not explore this vector.
A while ago one of my heroes, Tavis Ormandy mentioned on Twitter that
uBlock Origin was vulnerable to CSS injection in their filter rules.
I had a quick look at his injection vector and indeed I was able to
control more or less the full CSS of the injected filter rule:
example.com##div:style(--foo: 1/*)
example.com##div[bar="*/;background-image: url(https://google.com);}/
*"]
This was quickly patched but I managed to find a bypass that worked
in the latest uBlock Origin version:
##input,input/*
##input[x="*/{}*{background-color:red;}"]
After reporting this, I was informed by the developer Raymond Hill
that uBlock Origin also has cosmetic filters that allow more powerful
CSS selectors they even let you define your own CSS rules but are
restricted (limiting the use of URL requests), so I thought this
would be a good place to look for another bypass. In this context
cosmetic filters seem to use a different code path than normal
filters. You can't use backslashes inside rules for instance.
However, you can use an opening comment inside the selector and again
use two rules to smuggle unrestricted CSS:
*#$#* /* { font-family: ' background-color:red;'; }
*#$#* /*/ {background:url(/abc)} */ { font-family: '
background-color:red;'; }
This works because document.querySelector tolerates malformed
selectors:
document.querySelector('input[class');
document.querySelector('*/*');
document.querySelector('[class/*]')
document.querySelector('[class/*<>{}]')
document.querySelector('[class/*<>{}*/]')
uBlock Origin was using this function to validate the selector. They
fixed the comment bypass by looking for opening comments so I spent
some time with my CSS fuzzer to understand deeply what syntax is
allowed in CSS. I discovered some interesting behavior: inside a
selector you can use curly braces. They also have to have an open and
closing brace - because if they don't, then a semicolon will not
start a new rule:
This is not red
I found this behavior by running my fuzzer in reverse e.g. finding
which characters do not allow semicolons to create a new rule. For
instance using the fuzz vector $chr;background:url(red.png);}
highlighted curly braces as well as other characters. I then manually
verified you could use curly braces inside the selector:
This should be red
Using this knowledge I could bypass the patch using a vector that
doesn't use comments:
*#$#* {background:url(/abc);x{ background-color: red;}
After that was patched I began to look for ways to make background
requests. Fuzzing various properties and trying various techniques I
was unable to use the url() to make requests. So instead I started to
look for alternative CSS functions. I found a function called
image-set(), this function allows you to make requests with a CSS
string on Firefox and this works perfectly in uBlock Origin:
*#$#* { font-family: 'blah'; background:image-set('https://
hackvertor.co.uk/images/logo.gif' 1x) }
There is an alias called -webkit-image-set() which allows strings as
URLs on Firefox. Chrome has the function too but you must use it in
combination with the url() function.
Exfiltrating with CSS
If I could compromise a filter list then I would have control over
the CSS on every web site when using uBlock Origin but what could I
do? Most research on CSS exploitation has focused on attribute-based
selector attacks - because they make it quite easy to steal passwords
in inputs. David, Eduardo and I covered it in our CSS The Sexy
Assassin talk back in 2008! Stefano di Paola and Alex K. also had the
same idea. There has also been some excellent follow-up research from
Pepe Villa, Mario Heiderich et al, d0nut and Michal Bentkowski
covering all sorts of CSS exfiltration techniques. But there are
limitations: you can only read attribute values, so you usually can't
steal keystrokes. I began to think about what CSS I could inject to
steal content from the page.
So I decided to focus on custom fonts to see what was possible.
Custom fonts are great because you can choose the characters they get
assigned to. This allows you to steal those characters when a request
is made for the font. The Unicode range property allows you to select
which characters the font should apply to:
unicode-range: U+0061;
In this example the font will be loaded if the element contains a
lowercase "a". The trouble is that you can't get repeated characters,
and the font request is made for the entire content - not specific
parts of the element's text node.
First, let's make a custom font keylogger in CSS. This is a well
known technique but we first need to understand how stealing
keystrokes in CSS works and this is a great starting point.
steal-lowercase.css:
@font-face {
src: url("/a");
unicode-range: U+0061;
font-family: steal;
}
...
styles.css:
input {
font-family: steal;
}
CSS keylogger PoC
This creates a basic keylogger - the @font-face rule defines our
custom font, and makes a request to /a when the character is a
lowercase "a" - with a font-family of steal. This is then repeated
for the other characters. Styles.css simply assigns the font-family
to steal - which uses our custom font. The result is that when a
victim types into the input, a request is made for every character
you type (excluding repeated characters). But if you change the input
into a password field, the attack will fail. This is because the
characters get masked and therefore the custom font isn't loaded - as
the masked characters don't correspond to the unicode range defined
in the font.
This is true on all browsers, except when you unmask the characters
in Edge by clicking on the eye icon in the input. In this case, the
font is loaded and the characters stolen!
Screenshot showing Edge sending keystrokes when unmasking
An interesting thing about loading fonts on Firefox compared to
Chrome, is that Firefox loads them synchronously, where Chrome is
asynchronous. This has the advantage of leaking the characters in the
correct order in Firefox.
One thing I discovered is that you can make scripts display their
contents when using display:block. I later found out that Pepe Villa
had the same idea. This means we can steal the contents of a script
by assigning a font:
Stealing script contents PoC
This is great but has a limitation: it will try to steal the contents
of the entire script and any repeated characters will not be loaded.
What we are interested in is "supersecret" - is there any way to just
steal that?
I looked at ::first-letter selector - which (as the name suggests),
allows you to control the first letter of the text node. This works
for fonts so you can steal the first letter on Chrome, but it doesn't
let you steal the other characters, doesn't work in conjunction with
the :not selector, and doesn't seem to work on Firefox:
I then looked at the ::first-line selector. This selector is
interesting as it allows you to select part of the text. If I could
somehow force the text onto separate lines then maybe I could steal a
specific part of it. The ch unit allows you to specify the width of a
single zero character in the chosen font. I suspect zero is used
because it's more likely to be available in many different fonts.
This would allow you to break apart letters and force them onto the
next line.
My first attempt was to change the font inside the selector, assign a
width, and force it to break all the words apart. This worked in an
older version of Safari but not in Chrome or Firefox:
tester blah
Stealing first line on Safari PoC
It doesn't work in Chrome or Firefox because ::first-line only
supports a limited amount of CSS and when a font is assigned the
whole content is assigned the font not the ::first-line selector. How
could I make it work in Firefox? I thought about this for a while -
and although now it seems obvious, at the time it wasn't! How about
reversing the operation and using ::first-line as a mask? We could
then use the width to define a mask of characters we didn't want to
include! This means we could steal a substring of characters to the
length of the required string. This works perfectly on Firefox and
retains the order of the characters too:
some secret text
Screenshot showing technique to extract certain parts of the text
Stealing n characters on Firefox PoC
The above example will exclude the text "some" and steal the
characters after excluding repeated characters. Pretty cool eh? How
about the characters at the end though; we can't steal those if they
are repeated characters. Well, we can use CSS animations and animate
the mask to steal the contents backwards on Firefox!
We use the same template as previously but update the styles to
include an animation:
div {
width: 20ch;
word-wrap: break-word;
word-break: break-all;
overflow-wrap: break-word;
text-transform: lowercase;
font-family: steal;
animation-name: steal-reversed;
animation-iteration-count: 1;
animation-fill-mode: forwards;
animation-duration: 5s;
}
div::first-line {
background-color: green;
text-transform: uppercase;
}
@keyframes steal-reversed {
from {
width:20ch;
}
to {
width: 1ch;
}
}
So the mask starts at 20ch, we break the words as before. The
animation fill mode ensures it only plays and stops at the end - and
the animation-iteration-count ensures that it only plays once. The
result is the characters are stolen backwards.
Screenshot showing extracting characters in reverse on Firefox
Stealing characters reversed on Firefox PoC
Moving on from ::first-line, I started to look at the attr() function
in CSS. Unfortunately inputs will not allow pseudo elements, so
:before and :after will not work - which means that you can't use
attr() to extract attributes and assign them using content. There is
one exception to this: checkboxes. If you have a checkbox/radio with
a sensitive attribute, then you can extract it using attr() and
assign a font to steal the contents on Chrome:
Stealing attributes in checkboxes PoC
JavaScript URL injection
Finally, I spent some time trying to exploit uBlock Origin to try and
get a filter list automatically installed. I looked at how a user can
add a filter list from a webpage and I noticed that uBlock has an
allow list of domains that enable you to open the add filter dialog
in the extension. One of those allow listed domains is GitHub.com, so
you can use the following link on Github to open the add filter
dialog:
https://subscribe.adblockplus.org/?location=https://my-website/
filter.txt&title=EasyList
The domain "subscribe.adblockplus.org" doesn't actually exist but
uBlock Origin uses it to add the filter. It was possible to inject a
JavaScript URL, fortunately for them the extension's CSP prevented
exploitation. Nevertheless, uBlock Origin fixed the JavaScript
injection vulnerability too.
So if you could convince a user to add your filter - or maybe do a
pull request for one of the existing filters with an injection - then
you could use the techniques in this post to steal data. Another
approach would be to compromise a site hosting filters, perhaps using
web cache poisoning or HTTP request smuggling. Needless to say, we
didn't attempt any of this.
Conclusion
uBlock Origin provides powerful filters to control which elements are
allowed on a page. They can prevent malicious adverts from exploiting
your computer. However - if they are vulnerable to CSS injection, and
a user loads a malicious filter, then it's possible to exfiltrate
data from any web page using pure CSS. Ultimately, CSS-based
injection attacks can have a similar impact to XSS when an affected
page contains sensitive information that can be extracted.
Materials
All the attacks mentioned above can be downloaded from our Git
repository.
Disclosure
uBlock Origin does not provide a security contact or email address to
report vulnerabilities to. Therefore this was disclosed via their
public GitHub issues.
2021-08-25 05:16 - Tavis reported his bug
2021-08-25 15:09 - uBlock Origin patched Tavis' bug
2021-11-03 11:51 - I reported my bypass to uBlock Origin
2021-11-03 12:52 - uBlock Origin patched my bypass on master
2021-11-04 11:01 - Reported JavaScript URL injection vulnerability
2021-11-05 20:16 - uBlock Origin patched JavaScript URL injection
vulnerability
2021-11-08 13:25 - Reported bug in cosmetic filter
2021-11-08 14:19 - uBlock Origin patched my cosmetic bypass
2021-11-08 15:35 - I bypassed the patch without using comments
2021-11-08 16:18 - uBlock Origin patched the cosmetic filter bypass
2021-11-11 10:20 - Reported bug where image-set() was allowed in
Firefox
2021-11-11 20:21 - uBlock Origin patched image-set() vulnerability
2021-11-22 14:30 - uBlock stable released
2021-11-22 - Firefox version updated
2021-12-03 - Chrome version updated
2021-12-06 - Post published
CSS uBlock Exfiltration CSS injection
Back to all articles
This page requires JavaScript for an enhanced user experience.
Related Research
Creating a 3D world in pure CSS
13 October 2021 Creating a 3D world in pure CSS
Follow PortSwigger Research on Twitter
Burp Suite
Web vulnerability scanner Burp Suite Editions Release Notes
Vulnerabilities
Cross-site scripting (XSS) SQL injection Cross-site request forgery
XML external entity injection Directory traversal Server-side request
forgery
Customers
Organizations Testers Developers
Company
About PortSwigger News Careers Contact Legal Privacy Notice
Insights
Web Security Academy Blog Research The Daily Swig
PortSwigger Logo Follow us
(c) 2021 PortSwigger Ltd.