CSS Tricks
October 1, 2020
CSS Tricks #
CSS can be used to trick a user into exposing information such as embedded pixel values by making visual changes that are affected by the embed.
Retrieving user’s history #
Using the CSS :visited
selector, it’s possible to apply a different style for URLs that have been visited.
Previously it was possible to use getComputedStyle()
to detect this difference, but now browsers prevent this by always returning values as if the link was visited and limiting what styles can be applied using the selector. 1
So, it may be needed to trick the user into clicking an area that the CSS has affected.
This can be done using mix-blend-mode
. 2
There are also ways to do it without user interaction such as by abusing render timings.
This works because it takes time to paint links in a different color. 3
A Proof of Concept to this attack can be found in a Chromium report 4 and it works by having multiple links to increase the time difference.
info
This issue has been known for years and there are multiple open bugs to be fixed in multiple browsers 1 2 3. There are also publicily available proof of concepts, e.g. Whack a mole game 4 that requires user interaction for a successful leak.
Leaking status code #
Tricks to reveal a user’s history provide a way to detect the status code of a response cross-site. Chromium-based browsers will only save responses with OK status codes (e.g. 200) to the user’s history, but won’t add error status codes (e.g. 404). By visiting the target URL in a popup window, it will then either be saved to the history or not depending on the status code. After then placing the same URL on your page with <a href="...">
, it is possible to use one of the manual or automatic techniques described above to leak whether this URL was saved to the history or not.
In some XS-Search scenarios, a search with no results will return a 404 error. Cookies marked SameSite=Lax
will be sent in popup windows, and this technique allows such scenarios to be exploited. 5
Evil Captcha #
Using CSS, it’s possible to take an embed out of context.
An example of this is pretending it’s a captcha as seen in 6
This works by setting the width and height of an embed so that only the target characters are shown,
this may use multiple embeds to change the order of the characters being displayed so that its harder for a user to know what information they’re providing.
Abusing autocomplete #
If a website uses text inputs and does not opt-out of autocomplete using autocomplete="off"
it may be possible to leak data such as email addresses by tricking the user into pressing the keys to navigate the autocomplete UI for a javascript focused text input.
For Chrome, this requires the user to be tricked into pressing the Up or Down arrow key which opens the dialog and selects a value, then by pressing Enter or Tab the value gets inserted into the page.
let input = document.createElement("input");
input.type = "email";
input.autocomplete = "email";
input.name = "email";
input.size = "1";
input.style = "position:absolute;right:-500px;bottom:-21.9px";
input.onkeypress = e => {
e.preventDefault();
}
window.onmousedown = e => {
// ignore mouse clicks
e.preventDefault();
}
input.onchange = e => {
alert(e.srcElement.value);
e.srcElement.value = "";
}
document.body.appendChild(input);
setInterval(() => {
input.focus({preventScroll: true});
}, 1000);
Custom cursor #
A custom cursor might not leak data directly but it may help trick the user, as a large cursor may overlay the autocomplete dialog and other native UI.
<style>
:root {
cursor: url('https://www.google.com/favicon.ico'), auto;
}
</style>
Defense #
XFO prevents embeds from being attacked because there’s no visual difference as the content does not get shown.
The Retrieving user’s history attack can only be prevented by the user.
This can be done by disabling the browser history, or if on Firefox, by setting the option layout.css.visited_links_enabled
to false
in about:config
panel.
SameSite Cookies (Lax) | COOP | Framing Protections | Isolation Policies |
---|---|---|---|
❌ | ❌ | ✔️ | ❌ |