Web threats / Web skimmers

HexMage web skimmer

HexMage is a web-skimmer kit whose loader reads a per-site script host from an Ethereum Sepolia smart contract. We have matched it on 500 sites since 11 August.

HexMage in numbers

Since 11 August, the first HexMage match in our records, counted on 12 November 2026. How we count.

Sites with HexMage code500
Sites with a match in the last 30 days331
New sites in October81
Still serving it when recaptured at least 25 days later214 of 252 sites (85%)
Sites running WordPress (captured since 25 September)77%

A Tag Manager block that loads a skimmer

Confiant named HexMage in August 2026 after tracing the campaign's loader, contracts and checkout code (Confiant).

The loader sits in the infected store's page as a Google Tag Manager block. It sets a GoogleTagManagerLoaderScript global and pushes a gtm.start event, but never requests gtm.js. Its Base64 configuration contains Ethereum Sepolia RPC endpoints, a contract address and a path for that store's script.

From Sepolia to a per-site script

The loader imports ethers.js from a public CDN, connects to Sepolia and calls getText() on its contract. Early contracts return a Base64-encoded host. Later builds receive an encrypted string in salt:iv:ciphertext hex, derive an AES-256-GCM key with PBKDF2-SHA256 over 100,000 iterations from a per-site password in the page, _gwPwd, and decode the host in the browser. The loader appends its own resourcePath or scriptPath and adds the result as a script.

One current loader imports ethers 5.7.2 from jsDelivr or unpkg and calls Sepolia contract 0x3ad4d35f9b6f306792add1127a397b8824400bbc through five RPC services. On 1 November 2026 its reply decoded to stormwraith[.]com, and the loader added /2/<store>/vendor.chunk.h91s.js from that host, with the store's name in the path. One store carries a second inline loader beside it, reading Sepolia contract 0x1bfb4a0d7e1b8c552f0173c94b976fad2b4f0bc6 with a password of its own. On 2 November 2026 that contract decoded to morrowwillow[.]com.

Four older Sepolia contracts serve the same encrypted build: 0x3b14187b76dc39005e8d568b26455d53bd198749, 0xaed4fb9a79fbf3106f25a7986ad984e8a21a8795, 0x61b3e0681f6485e4e85e5ee981757d8b12ec414f and 0x5a155a3f01fa60c3fa0177022ba05fcc6a313a4d. Their replies decode to ironphantomcore[.]top, zaevoderix[.]com, stormvenomforge[.]top and shadowemberstrike[.]top, and the loaders add paths such as 6/topl.js and 5/proftop.js. In these loaders the password is the only 64-character hex string in the obfuscator.io string array. A loader captured on 10 November 2026 reads Sepolia contract 0x304dda3b4738d7a4f5a2a423d8cc8c883ff8dd5d with the selector 0xe00fe2eb and decrypts the reply the same way, to ashenshadowfort[.]top. One captured on 12 November 2026 has the same Tag Manager wrapper, ethers 5.7.2 import and selector, but its contract answers in plain Base64, the early format, and the host reads without the password. The EtherHiding contract tracker follows every HexMage contract here and each destination we have decoded from them.

The per-site chunk decodes in the store's browser

The script at the resolved URL derives an XOR key from location.hostname, after removing www. and converting the hostname to lower case. It decodes a Base64 string, applies the hostname-derived key and unwraps further Base64/XOR layers. The decoded JavaScript is placed in a Blob and loaded through a script element with a blob: URL.

The chunk served from the contract-resolved URL saves its original script directory in window.__gwScriptBase for the decoded code to use after Blob execution. Decoded checkout scripts target WooCommerce, Stripe and FullCulqi card forms. Report URI documented the same contract-resolved skimmer campaign and its staging hosts.

A stage built for each store

Newer stages are obfuscator.io builds served from a path named for the victim, such as /2/kracik/chunk-o18u22nfonc.js or /migratedfiles/5/hediye724/shield-performance.js. Each store gets its own build. The string array in these builds is plain Base64 with no RC4 key, so all of its strings read without running the code. The stage keeps its state in _gw globals, _gwShowingPopup, _gwPopupSubmitBlocked and __gwScriptBase, the same prefix as the loader's _gwPwd.

stormwraith[.]com serves an ordinary page at its root, and the skimmer sits only at the per-store paths. The stage hosts we have found:

stormwraith[.]com
willowtransit[.]com
morrowwillow[.]com
morrowtheory[.]com
mildrocket[.]com
voidhunterkeep[.]top
nightstalkerwatch[.]top

The WooCommerce checkout overlay

The skimmer watches WooCommerce checkout events and uses a MutationObserver to keep its payment form in place when the checkout refreshes. It leaves logged-in WordPress administrators alone.

The form collects the card number, expiry, CVV, cardholder name and billing email. It checks the card length and Luhn checksum before accepting it. The newer stages hook input[name="payment_method"] and the updated_checkout event, handle cash on delivery, and draw a pf50_card-form with Stripe card-brand icons and labels in the store's language, Arabic on one victim.

The captured values are Base64-encoded and sent by whichever method the build enables: an image request, a fetch GET with a ?data= parameter, navigator.sendBeacon or a WebSocket. When none works, the stage logs All enabled send methods failed. After the send, the overlay disappears and the store's original checkout returns, sometimes with a page reload.

What we see

MonthNew sites
September346
October81
November so far27

Of the 252 sites recaptured at least 25 days after their first match, 214 were still serving HexMage (85%). 77% of HexMage sites captured since 25 September run WordPress.

Checking a store

Inspect the checkout page source and network log for:

  • a GoogleTagManagerLoaderScript block that never loads gtm.js and imports ethers.js from a public CDN on a store with no blockchain feature
  • calls to Ethereum Sepolia followed by getText() and a new script element, with resourcePath, scriptPath, PBKDF2 or AES-GCM in the loader
  • a per-site chunk that derives an XOR key from location.hostname and unwraps Base64/XOR layers
  • window.__gwScriptBase followed by a JavaScript Blob and a blob: script URL
  • a second payment form placed over the WooCommerce checkout
  • local-storage keys beginning with mn_, or _gw globals such as _gwPwd and _gwShowingPopup
  • a script loaded from a path containing the store's own name on one of the stage hosts above
  • card or billing data sent to an unfamiliar host by fetch, WebSocket, beacon or image request

Remove the loader, its linked script and cached copies. Compare the checkout, theme and plugin files with trusted releases, review WordPress and hosting access, rotate credentials, clear caches and test the checkout while logged out.

Contracts we read

We read 35 HexMage contracts every day.

ChainContractCurrent destinationsLast changed
Ethereum Sepolia0x04141d92BaA052a778dD86cCcC1287D870a2A06Cstylerunningg[.]com
Ethereum Sepolia0x0f57ecf9c92ff364bd30aa64f4c916c458b0e70emarblehabit[.]com
Ethereum Sepolia0x10ef316107c7a499b8b1f28a05cd858f49c0bae9wildreceipt[.]com
Ethereum Sepolia0x1bfb4a0d7e1b8c552f0173c94b976fad2b4f0bc6morrowwillow[.]com
Ethereum Sepolia0x1cf9f972ffb05f8f8c32705dccb6548a7cbae496stylehailens[.]com
Ethereum Sepolia0x28b7605be52b7107cd7710f6f855a1a026e72f4anightflamevortex[.]top
Ethereum Sepolia0x2979ed1f3eacba7391c667d2a6cb866a25b45719everthimble[.]com
Ethereum Sepolia0x2ea6aa15cab85d071c42dac4231f0fa1cee3176belectricvoidsig[.]top
Ethereum Sepolia0x304dda3b4738d7a4f5a2a423d8cc8c883ff8dd5dashenshadowfort[.]top
Ethereum Sepolia0x3ad4d35f9b6f306792add1127a397b8824400bbcstormwraith[.]com
Ethereum Sepolia0x3b14187b76dc39005e8d568b26455d53bd198749ironphantomcore[.]top
Ethereum Sepolia0x4b6170a49090fae965527e222515c2224fe73dd5voidwalkerforge[.]top
Ethereum Sepolia0x4dbdca7277427edee4d5b4e3d012bca73aefadf1voderlence[.]com
Ethereum Sepolia0x50b9acc5a99b28117428e143b6eed6d36a703395shadowmatrixpls[.]top
Ethereum Sepolia0x515cfebd53a860c311d583b74a8b02a78a7884e0shadowrunevail[.]top
Ethereum Sepolia0x51e3f029209c210fa8946c219644394aae71224dfrozennebularhy[.]top
Ethereum Sepolia0x52022366d66464a4d34c3a6ebfa3b47817d6d0b1orchardvector[.]com
Ethereum Sepolia0x5a155a3f01fa60c3fa0177022ba05fcc6a313a4dshadowemberstrike[.]top
Ethereum Sepolia0x5a3589462b41fa8cf91ac2c7773a285d9c790548velnoderic[.]com
Ethereum Sepolia0x5ea589954314fe8d5ecb509b35d77a389912b42fvoidravenstorm[.]top
Ethereum Sepolia0x61b3e0681f6485e4e85e5ee981757d8b12ec414fstormvenomforge[.]top
Ethereum Sepolia0x7198012e79fdd490b7c95fdffc6cc65929332c0fpaleanchor[.]com
Ethereum Sepolia0x8ac95e2af13326ddbfd53b155923745751cd7d0amossparlor[.]com
Ethereum Sepolia0xa4d63e7825d5010a1aa77f55bc2a45cecf2b028ehiddenmorrow[.]com
Ethereum Sepolia0xa923654195be9e83144032d8dfc83cd5dfc1f981morrowtheory[.]com
Ethereum Sepolia0xaafb11bf4199f7499b7f4dbb3d566f2bc13a6535ghostharvest[.]com
Ethereum Sepolia0xaed4fb9a79fbf3106f25a7986ad984e8a21a8795zaevoderix[.]com
Ethereum Sepolia0xaf59a46ab7270adf919ea08e9250a5783a78721eblackthornhold[.]com
Ethereum Sepolia0xbcccb3909b0eec36211ddafb3ccdc6c2a537bea1ivoryfable[.]com
Ethereum Sepolia0xd6e035c294b3b87c5f86ccec3f62dc3cad8a88e9hollowsentinel[.]com
Ethereum Sepolia0xdfcef1c30f453d206fa32c704a0fd4d1fee1e6f7mildrocket[.]com
Ethereum Sepolia0xeaa29047855f6761ad7cb95eda01145acd658b2fsilvermorrow[.]com
Ethereum Sepolia0xec911c2e254bb62fbdc524841b154adbfa8869efvoidhunterkeep[.]top
Ethereum Sepolia0xedda0630ea1db3662222a413eb034c9cffa756d1quietthistle[.]com
Ethereum Sepolia0xee572bcc785594e06eacea2e6ef638e087d8fac5cedarfable[.]com

The EtherHiding contract tracker keeps the full destination history.

Try the feed for 14 days.

Request a trial and we'll send you a live API key, with the same feed, removals and reasons a customer gets.