Friday, 6 May, 2022 UTC


Summary

About a month ago, we came across a report about the discovery of another magecart infection. Even though this infection was first detected in a known consumer electronics retailer, the same skimmer was also detected being used on over a dozen other websites.
One of the most peculiar aspects of this attack was the fact that it was fetching a remote file called “fonts.css”, from which it would retrieve javascript code to be executed as the next stage of the attack.

But how can a .css file contain javascript code?

Well, to be fair, it didn’t. The .css file actually contained a few lines of CSS code, but right after that valid code, there was a huge amount of non-visible characters such as spaces, tabs, and newlines. There was not a clear pattern in this sequence of characters, so this was very suspicious and strongly hinted that steganography was being used.
Image 1: Invisible characters selected in fonts.css

So what is steganography and how does it work?

“Hiding in plain sight” is the most common description for the use of steganography. It consists in hiding a secret message in something that is not secret and this practice goes back, at least, as far as ancient Greece. One of the most famous examples from this era is the fact that they would shave a messenger’s head, tattoo a secret message into his scalp, and then send the messenger to his destination after his hair had grown back.
In comparison, the malicious script we are analyzing conceals the malicious javascript code behind spaces, tabs, and newlines, which are visible to everyone, but it needs a “haircut” process to reveal the hidden message. This process consists of 3 major steps:
  1. Split the payload into chunks by using space characters as the separators
  2. Replace tabs with the character "1"
  3. Replace newlines with the character "0"
Image 2: Converting fonts.css contents to a binary string


The result is a binary string that represents obfuscated javascript code ready to be executed in the next stage of the attack.

Image 3: Example of the transformation process

What was responsible for this processing or fetching the .css file in the first place?

Turns out the victim’s website was hosting and running a compromised version of jQuery, but we do not know for sure how it got there. The victim may have simply downloaded an infected version of jQuery from an untrusted source or have some other kind of vulnerability in the application, or their servers, that would allow an attacker to modify an existing script. However, these are only a few possibilities among many others.
This compromised script was simply a legitimate minified version of jQuery, but with an additional malicious function appended to the bottom of the script, commonly seen on other magecart attacks, like the one that targeted British Airways. This function was responsible for fetching the .css file, processing the hidden payload, and finally executing the generated javascript code.
Image 4: Compromised jQuery event sequence


Does this mean that a suspicious URL was visible in the code?

This is where the attackers used another simple but effective trick. Using a combination of a decoy URL that resembles a typical Facebook CDN endpoint, and an array of numbers representing specific indexes, the attackers can pick characters from the decoy URL to construct the real malicious URL.
Image 5: Converting the decoy URL to the malicious URL


But how was the sensitive information accessed?

In the next stage, the attackers add an event listener for the “onblur” event to the “window” object, which will trigger whenever the window loses focus. When this happens, another function will add “onchange” event listeners to all elements of type “textarea”, “select” and “input”. Every time there is a change in the value of these elements, both an identifier and the value of the element will be temporarily saved in a “localStorage” item.
Image 6: Saving collected data into a localStorage item

What about the exfiltration process?

The attackers chose to create 2 additional event listeners on the “window” object. These listeners, triggered by “DOMContentLoaded” and “beforeunload” events, would exfiltrate the data every time the page had finished loading its content or the user decided to leave.
At this point, the script would grab the saved data from the “localStorage” and perform a sequence of transformations. It encodes data as “base64”, reverses it and converts it into a string of tabs and newlines according to its binary representation. The binary data is then used to create a new Blob object that is appended to a “FormData” object.
Finally, the script executes a “POST” request using “fetch” and sends the “FormData” object containing all the stolen information to a URL controlled by the attackers. When this is completed, the attackers remove the “localStorage” item they have been using to store the information, cleaning any traces of their operation.


So what do we learn from this?

Tracking digital skimmers is getting harder by the day. There are countless threat actors and variations of skimmers, as attackers continue to look for new, improved and automated ways of deception. If you own an e-commerce website, make sure to keep your software updated, try to leverage the best security practices available and, if it suits your needs, try to adopt a solution for client-side in-depth protection, like Jscrambler.