HexMage, Ten Weeks Later: 533 Infected Shops and Counting
Ten weeks ago, Confiant published research into a Magecart campaign they named HexMage, uncovering a card skimmer that used Ethereum smart contracts to hide its delivery infrastructure.
We've been tracking the same campaign independently, and our latest findings show that it's considerably larger than the original research suggested.
We've identified 533 online shops currently infected with HexMage, traced 183 smart contracts deployed by the attacker, and uncovered new developments as the operator continues their activity.
There's some good news, too. Of the 26 shops named in Confiant's original research, 25 appear to have cleaned up their infections. Unfortunately, we've found hundreds more.
We first wrote about this malware in October, in The Fake Stripe Card Form Skimming Online Shops, before we'd connected it to Confiant's findings.
Now that we've established we're tracking the same campaign, I wanted to take a closer look at how HexMage has evolved, what the attackers have been doing since the original disclosure, and what we can learn from the infrastructure they've left behind.
Most of the named shops cleaned up
Starting with the shops Confiant named in their blog post, 25 out of 26 of them have now been cleaned up, but there is still one that is loading the skimmer today. After some analysis, we had reliable indicators to detect HexMage, so we went hunting for more.
Going back over our existing threat intelligence data we were able to identify a total of 533 shops that are currently infected with the HexMage strain. 487 of those are using the blockchain loader, but 48 of them are using a simpler version with the address hard-coded in base64. We found a few stores that were carrying both versions too.
A total of 183 contracts
Every HexMage contract we've seen was deployed by the same wallet Confiant identified, 0x88361C914Bb0942da9a1b7Bb396a7513C1917aee. Blockchains keep everything, so its full history is public, and the wallet received a single payment of test ether on 28 March, and it has been deploying contracts ever since:
| Month (2026) | Contracts deployed |
|---|---|
| March | 12 |
| April | 43 |
| May | 27 |
| June | 35 |
| July | 27 |
| August | 12 |
| September | 26 |
| October | 0 |
| November | 1, deployed 19:44 UTC on the day of this research |
That's 183 contracts in total, with Confiant counting 156 of them up to 23 August, so 27 have been deployed since their report. September comes straight after their report, and it was one of the busier months, before things seemingly cooled off in October. We can only wait to see what happens in November and if that single deploy is a sign of things picking up again.
A contract's whole job is to be changeable: one transaction points every shop that uses it at a new host. That's the selling point of hiding infrastructure on a blockchain, yet across 183 contracts, the wallet has changed a stored value only 16 times, and only once since the end of June. This particular operator doesn't re-point old contracts, it deploys new ones instead, one batch at a time, with new hosts behind them. It's not the usual approach.
Looking at the hosts used in these contracts, they come in three naming styles, one after another. style glued to a word (stylerightnoww.com, styletimeset.com) through April. .top domains through May and June. Since July, fantasy and pastoral .com names (morrowwillow.com, paleanchor.com, wildreceipt.com). Seven of the domains Confiant listed as registered but unused have now come to life and are live and delivering the skimmer payload.
Encryption used as obfuscation
Until May, the contracts stored their next-stage host in plain text that could simply be read. Since then, every new one stores ciphertext instead: AES-GCM, with the key derived from a password using PBKDF2. On paper, that means reading the contract no longer tells you where the skimmer lives because the content is encrypted.
In practice, the password has to reach the shopper's browser somehow because it has to decrypt that payload, so it's in the injected snippet on every infected page, base64-encoded in a variable called _gwPwd. With the page and the contract, decrypting takes only a few lines of code, and for 126 of the 183 contracts we had a victim page and could recover the password to decrypt the host.
The other 57 remain a mystery and without a corresponding infected page, we can't recover the passwords needed to decrypt their contents. Some may be linked to shops we haven't discovered yet, while others could be unused or abandoned infrastructure. Either way, there are still plenty of leads to follow.
Inside the newest builds
We collected 13 different copies of the skimmer, with at least one from every month the campaign has been running, and no two were the same file because every shop gets its own build. This rules out simple protection and detection approaches of blocking by file hash.
Since May, each build is wrapped in layers before the skimmer code appears:
- A layer that only unlocks on the victim's own site. The payload is XOR-encrypted with a key built from a short per-build prefix and a hash of
location.hostname. Confiant documented this hash. Load the file anywhere else, in a sandbox or from a researcher's machine, and it decrypts to garbage and does nothing. - A second XOR layer with three keys, applied in reverse order.
- A
blob:URL. The decrypted code is wrapped in aBloband loaded as a script from ablob:URL, so the final payload never exists as a fetchable file.
Underneath all of that, every one of the 13 builds is the same kit Confiant described: the same debug strings, the same fake card form, the same Russian month names left in by its authors. It still waits for the shopper to press the checkout button, and it still borrows Stripe's card-brand icons to make its fake form look real.
A builder config with a CSP switch
Each build starts with a configuration object, the kind a builder panel would produce, and here are some of the options:
| Option | What it does |
|---|---|
serverUrl |
Where stolen card data goes. In every build we decoded, it's a .php file in the same per-shop directory the skimmer was loaded from, on the same host. |
excludeAdmins |
Stays quiet for logged-in shop administrators, the people most likely to notice. |
devToolsDetection |
Behaves differently when DevTools is open. |
enableFetchGet, enableBeacon, enableImagePixel, enablePhpProxy, enableWebSocket |
Five interchangeable ways to send the data. |
enableStats |
A tracking pixel so the operator can count views. |
enableCspBypass |
Instead of sending the data in the background, it navigates the shopper's whole tab to cspProxyUrl?data=…. |
That last option exists because its authors clearly know about Content Security Policy. A policy's connect-src controls the requests a page can make in the background, but it doesn't stop the page navigating somewhere else, and no widely supported directive does. A properly configured script-src defeats this mode anyway, because the skimmer never gets to run in the first place.
We checked the 531 infected shops we could reach and 386 send no Content Security Policy at all, and most of the rest only use one to upgrade insecure requests. Only one has a policy that would stop HexMage, and on that shop it does: the loader runs, but every request after it is blocked. That shop doesn't know, though, because none of the 145 policies we saw sends violation reports anywhere, so a CSP that blocks the attack still leaves the owner unaware they've been compromised.
Where CSP stops it
HexMage needs the shopper's browser to make several requests, and each one is a chance for a Content Security Policy to stop it and tell you:
script-srcblocks the delivery host. For the blockchain variant, it also blocks the copy of ethers.js loaded from jsDelivr or unpkg, unless your policy allows the whole CDN.- A
script-srcthat doesn't allowblob:blocks the final stage of every build since May, because the decrypted payload is loaded from ablob:URL. connect-srcblocks the JSON-RPC calls to public Sepolia endpoints. It also blocks thefetch()that sends card data back to the delivery host.- Reporting turns each of those blocks into a violation report from the shopper's browser, which is the one place the server-side checks and the shop's own monitoring can't see.
Report URI's client-side security platform collects and analyses those reports. Every HexMage delivery host we could recover is in our threat intelligence feed, so any site sending us CSP reports gets an alert if its pages try to load one. You can deploy our monitoring in a matter of minutes, and our 30-day free trial requires no credit card.
How to check your own site
View the source of your checkout page (the HTML the server sends, not the live DOM) and search for:
GoogleTagManagerLoaderScriptresourcePath=atob(orcontainerId=atob(_gwPwd
The real Google Tag Manager snippet uses none of these, and it always loads googletagmanager.com/gtm.js, which these imitations never do.
In DevTools, a checkout that makes JSON-RPC requests (eth_chainId, eth_call) to a Sepolia endpoint, or loads a script from a blob: URL that you didn't add, needs a closer look.
If you find it, removing the snippet isn't enough. Someone had enough access to put it in your page head, so treat it as a compromise of your site's admin access or files, and treat any card entered at checkout since then as exposed.
Indicators of Compromise
| Type | Value |
|---|---|
| Owner wallet (Sepolia) | 0x88361C914Bb0942da9a1b7Bb396a7513C1917aee |
| Contracts | 183 deployed by that wallet, 2026-03-28 to 2026-11-05 |
| Contract functions | getText() selector 0xe00fe2eb, setText(string) selector 0x5d3a1f9d |
| In-page markers | GoogleTagManagerLoaderScript, containerId=atob(, resourcePath=atob(, moduleSources=JSON.parse(atob(, _gwPwd, window.__gwScriptBase |
| Delivery path | https://<host>/<n>/<shop>/<name>.js |
| Exfiltration path | https://<host>/<n>/<shop>/<word>-<word>-<word>.php (same host and directory as the delivery script) |
Delivery hosts not in Confiant's report or our October post (16):
brightmorningday.top coolmusicnight.top electricvoidfire.top everthimble.com
happyweekendfun.top peacefulmindset.top pixellowersoon.top quietcityevening.top
stylebackisn.com styleiksas.com styleohmans.com styleoutsperee.com
styleuskisd.com stylevors.com stylewowcafwe.com velvetcairn.com
The rest of the hosts are in Confiant's report and our October post.