ClickFix via Cloudflare Zaraz and the BW Panel
A malicious JavaScript action served through Cloudflare Zaraz on edgeupstudio[.]com loaded a BW Pane 2026-8-28 21:26:21 Author: www.derp.ca(查看原文) 阅读量:7 收藏

A malicious JavaScript action served through Cloudflare Zaraz on edgeupstudio[.]com loaded a BW Panel bootstrap used by ErrTraffic ClickFix campaigns.

The Zaraz code handles loading and module selection. The chosen module draws the fake verification screen.

Internal portal showing threat activity confirmed for the Zaraz-delivered BW specimen

Figure 1. Internal portal confirmation for the Zaraz-delivered BW specimen. The portal detected the returned /cdn-cgi/zaraz/s.js response and tagged it as the BW Panel Zaraz XOR-6 Pageview wrapper.


Attack flow

The chain has five parts:

  1. A malicious Pageview action reaches the browser through the site's Cloudflare Zaraz endpoint.
  2. Base64 decoding and a single-byte XOR operation reveal the BW loader.
  3. The loader asks a Polygon contract for the current panel address.
  4. The loader encrypts its request and contacts /api/index.php on that server.
  5. The panel returns one of nine scripts, including fake browser, CAPTCHA, and Cloudflare prompts.
Five-phase attack chain from Cloudflare Zaraz delivery through ClickFix delivery paths

Figure 2. The recovered attack chain from Cloudflare Zaraz delivery through the BW Panel and its ClickFix presentation paths.

Phase 1: Cloudflare Zaraz delivery

Cloudflare Zaraz is a legitimate tag-management platform that runs scripts from Cloudflare's edge. On the compromised site, its inline bootstrap gathered page state and loaded the first-party endpoint /cdn-cgi/zaraz/s.js?z=<encoded page context>.

The response contained a malicious Pageview action. Because the browser requested it from edgeupstudio[.]com, the first step looked like a normal first-party site resource rather than a script loaded from an unrelated domain. The /cdn-cgi/zaraz/s.js path itself is a normal part of Cloudflare Zaraz.

In April 2026, YHL found the same loader in a malicious Cloudflare Worker (opens in new tab) that proxied origin responses and appended an encrypted script. The decoded payload used site_repair_state, bw-downloaded, __BW_MODE_RUN__, the same nine file names, and selector 0xb68d1809. It pointed at a different Polygon contract, marking it as a separate deployment of the kit.

Observed inline bootstrap excerpt

window.zaraz._p = async nK => new Promise(nL => {
    if (nK) {
        nK.e && nK.e.forEach(nM => {
            const nP = d.createElement("script");
            nP.innerHTML = nM;
            d.head.appendChild(nP);
        });
    }
    nL();
});

lX.src = "/cdn-cgi/zaraz/s.js?z=" +
    btoa(encodeURIComponent(JSON.stringify(lP[lR])));

Phase 2: Payload unpacking and obfuscation

The returned Zaraz response contained an inline obfuscated payload. Its unpacking routine decodes Base64, applies a single-byte XOR with key 0x06, decodes the result as UTF-8, and executes it through new Function().

This wrapper keeps the loader's decoded strings from appearing directly in the response body and prevents straightforward matching against its readable text. The XOR is easy to reverse once the key is known.

(function() {
    var _0xb0c5b4 = 6;
    var _0xaaa71f = "<base64 payload>";

    function _0xe89dd1(s, k) {
        s = atob(s);
        var len = s.length, i, arr = new Uint8Array(len);
        for (i = 0; i < len; i++) {
            arr[i] = s.charCodeAt(i) ^ k;
        }
        return new TextDecoder("utf-8").decode(arr);
    }
    var decoded = _0xe89dd1(_0xaaa71f, _0xb0c5b4);
    (new Function(decoded))();
})();

The same operation in Python:

import base64

def deobfuscate_zaraz_action(encoded_payload: str, key: int = 0x06) -> str:
    raw_bytes = base64.b64decode(encoded_payload)
    unmasked = bytearray(b ^ key for b in raw_bytes)
    return unmasked.decode("utf-8", errors="ignore")

Phase 3: EtherHiding through Polygon

After unpacking, the loader needs the address of its panel server. It does not contain that domain directly. Instead, it sends a read-only JSON-RPC eth_call request to a smart contract on Polygon and decodes the server address returned by the contract.

Why use EtherHiding?

The contract acts as a public lookup record. Its code and transaction history remain on the blockchain, but the stored panel address can change. Every copy of the loader keeps calling the same contract and picks up the replacement address automatically.

The panel domain is still visible when the browser connects to it and can be blocked. Putting the address in the contract lets the operator replace it with one update instead of changing the loader on every compromised site.

The lookup also runs through shared Polygon RPC services. Blocking every one of those services would interfere with legitimate applications that use the same infrastructure.

The loader can query QuickNode, Ankr, Nodies, BlastAPI, 1RPC, DRPC, Tenderly, Tatum, SubQuery, TheRPC, Lava, PublicNode, and HyperSync.

What does `0xb68d1809` mean?

Ethereum-compatible contracts use a four-byte function selector to decide which function a request wants to call. The selector is the first four bytes of the Keccak-256 hash of the function's signature. In this loader, 0xb68d1809 asks the contract for the current panel address.

Calling this selector "non-standard" only means it belongs to this custom contract rather than a widely used contract interface. Its exact value is repeated across BW Panel deployments, which makes it useful as a family fingerprint.

BlueTeamCoolTeam mapped four operators using the BW Panel kit (opens in new tab) with this selector, the same site_repair_state storage key, the same __BW_MODE_RUN__ function, and the same nine-file lookup table. Its July 2026 revalidation counted 348 unique compromised sites across those operators.

Edgeupstudio's loader uses the same selector, storage keys, runner name, and file map.

Representative JSON-RPC request:

{
  "jsonrpc": "2.0",
  "method": "eth_call",
  "params": [
    {
      "to": "0x224579e572cEEc5309A7d9F5fAf85dea5dBb7D4A",
      "data": "0xb68d1809"
    },
    "latest"
  ],
  "id": 1
}

After decoding the address returned by the contract, the loader contacts the panel over HTTP or HTTPS for its configuration, presentation scripts, telemetry endpoints, and payload-delivery API.

Phase 4: BW Panel requests and encryption

The unpacked JavaScript contains the BW Panel's storage keys, global runner function, static API key, contract settings, and list of presentation files.

const BW = {
    storageKey: "site_repair_state",
    legacyStorageKey: "bw-downloaded",
    runner: "__BW_MODE_RUN__",
    contract: "0x224579e572cEEc5309A7d9F5fAf85dea5dBb7D4A",
    selector: "0xb68d1809",
    apiKey: "cb9ef8804138983511a1dd21ad6839d03c00a9672807de414ec5eaaa7eb4390a",
    modes: {
        browser: "v1.js",
        font: "v2.js",
        recaptcha: "v3.js",
        bsod: "v4.js",
        silent: "v5.js",
        cloudflare: "v6.js",
        cf_update: "v7.js",
        mac_recaptcha: "v8.js",
        mac_cloudflare: "v9.js"
    }
};
Encrypted query flow showing nonce generation, RC4 key derivation, and base64url request encoding

Figure 3. The encrypted query structure used for BW Panel API requests.

For each request, the client creates an eight-byte nonce, a value used once. It uses crypto.getRandomValues() and falls back to Math.random(). The client joins the static API key and nonce to make the RC4 key, encrypts the query string, and sends q = base64url(nonce || cipher) to /api/index.php. In that expression, || means the nonce and ciphertext are concatenated.

The encrypted value contains the request that would otherwise appear in the URL, including a=cfg, a=evt, a=dl, or a=js&mode=cloudflare. This keeps those action names out of plain-text URLs and makes simple network matching less reliable.

Trinity Cyber's ErrTraffic brief (opens in new tab) documents the same sequence: Polygon resolves the panel, RC4-encrypted request values travel in the q parameter, and loadModeScript() retrieves the selected XOR-wrapped presentation module. The brief identifies this loader and panel protocol as the 2026 ErrTraffic build.

Event telemetry travels as a plain JSON body sent to the same API URL. The loader can decrypt two response formats:

  • enc=q2: RC4 using the static base key plus the response-envelope nonce.
  • enc=gcm1: AES-GCM support for authenticated payload decryption.

Phase 5: ClickFix presentation modules

A "mode map" is a lookup table that pairs each mode name with the JavaScript file that implements it. If the panel selects cloudflare, for example, the loader requests v6.js.

Inside the `cloudflare` module

The cloudflare entry points to v6.js, the Windows ClickFix presentation module. PKF Algosmic published a decoded copy (opens in new tab) showing what happens when the panel selects it:

  • Renders a fake Cloudflare verification page inside a closed Shadow DOM, isolating the overlay from the compromised site's styles.
  • Requests a delivery token and metadata from /api/index.php.
  • Generates an obfuscated PowerShell command and writes it to the visitor's clipboard.
  • Reports clip_ok or clip_fail and polls the token-status endpoint to track the next stage.

Technical indicators


文章来源: https://www.derp.ca/research/zaraz-clickfix-bw-panel/
如有侵权请联系:admin#unsafe.sh