Time for #DevDiscuss!— DEV Community 👩💻👨💻 (@ThePracticalDev) August 14, 2019
Tonight's topic: CSS can do that?
Let's start with some questions:
- What is a CSS feature not everybody knows about?
- What has changed about CSS capabilities in the past few years?
- How do you approach browser support issues and fallbacks? pic.twitter.com/eg9THMsVnT
Inspired by today's #DevDiscuss I commented with my favourite misdeeds in CSS.
Did you know you can do user tracking of clicks/mouse movements/etc. only with CSS?— Aaron Powell (@slace) August 14, 2019
How about creating a keylogger?
Yep, all that's possible with CSS #DevDiscuss https://t.co/vIzJdSHNp7
So let's have a look at how they work.
We have our selector like so:
Assume it's repeated for every character you want to log.
The important part of the selector is the substring match on
value, this part:
[value$="a"]. This is an attribute selector, specifically a substring selector that was added as part of CSS 3 and what it's doing is saying is that it'll match when the
value attribute of the DOM element ends with
a (you can use
^ for begins with if you wanted).
So we're matching when the
value attribute contains that but if you were to look into the DOM of a form on a page you'll notice something, the
value attribute isn't set. Here, take a look at this:
If you open up the dev tools in your browser you'll notice that when you type in the input the attribute doesn't change, it's always set to
document.getElementById('demo-01').value it'll have what you entered. This is because the attribute represents the default value of the
<input>, not the current value, that's something that might get computed, depending on the type of input you have.
value attribute along the way.
What this does is it “pretends” that you're doing your keypress appropriately by catching it early and then pushing the character you intended to enter onto the
value attribute, making it look like you were typing normally. We then use the
setSelectionRange method on the input to position the caret to the end of the input so you are none the wiser. A demo can be found here of this in action.
For example, React synchronises the
value attribute with state if you're using a controlled form, which is something that this issue tracks. So if you're on a website that is using React then that website is vulnerable to this kind of an attack, whether it's through an extension in your browser or some dodgy ad running on the site.
I just want to quickly touch on some points the author makes in this post. They state that it's not really a big deal because the
background-image is only done for the first match so repeated characters won't pick up (e.g. a password of
password will miss a
s), and that is true (the
value didn't change the last character at
pass so the selector wasn't triggered) but the data capture will include timestamps and if you take a level of variance between the timestamp of events you can extrapolate your own gaps (if it took 0.1ms between captures and then there was an 0.5, maybe some characters were duplicated). The same goes for the observation that the order-of-receive isn't guaranteed. That's true, the server may receive them out of order, but when you have all (or 90+%) characters of a password the ability to brute force goes down drastically.
User Tracking with CSS
This is not quite as scary as a keylogger but it does borrow the same underlying principle as the keylogger.
For this we're going to exploit CSS Pseudo-Classes, which allow us to hook into a number of events of DOM elements.
Hover over me
Here's the CSS that I applied to those elements:
I'm just using pseudo-classes like
:active to know when you've done something and then change some colours, but again I could be setting the
background-image to a tracking URL.
How could this be made useful? Well, think of it like implementing Google Analytics, you could do something like attach a
:hover state to the
body element so you know when the page is appearing for the user and then more hover states on all the child elements; as the user moves around the page you're capturing the rough position of their cursor and knowing what they are spending their time on. If there's a form you can work out how long they spent on each field, how they navigate forwards and backwards through a multi-step form, or if they change answers on radio buttons/checkboxes.
CSS is Turing Complete
Ok, CSS + HTML if you want to be pedantic but it's true, it is possible to implement Rule 110 with just CSS and HTML:
Credit to eliheeli on GitHub for the working example of it.
This works by abusing Pesudo-classes like our tracker and combining those with the Adjacent sibling combinator. The adjacent sibing combinator, or
+ for short, works like this:
I'm a paragraph.
I'm an adjacent paragraph
Here we're applying a rule to all
p elements, but then we're using the adjacent sibling selector to apply a rule to the 2nd
p only (in this case, turning on a different font family and style). By applying conditions on the first half of the selector, such as a pesudo-class, the cascade of the rules can be greatly limited.
Emoji Class Names
Who doesn't love themselves a liberal usage of Emoji's throughout their work? Well did you know that you can use Emoji as the class names in CSS? According to the spec they are technically valid, meaning you can do this:
In reality you probably shouldn't do this, but hey, you could shave a few bytes over the wire for the sake of a few users not being able to access your site (or dev on your codebase)!
What started from a throw-away tweet became the catalyst for writing a post I've been meaning to do for a few years now! 🤣
I hope you've enjoyed a look at a few things you can do with CSS, but maybe shouldn't.
What are your favourite ways to exploit CSS?