Web threats / Web skimmers

RTCGnaw web skimmer

RTCGnaw is a Magecart card skimmer for WooCommerce and Magento shops. Its loaders read their next stage from blockchain contracts, and its controllers send each infected shop a skimmer built for that shop's checkout. We have matched it on 26 sites since 4 September.

RTCGnaw in numbers

Since 4 September, the first RTCGnaw match in our records, counted on 12 November 2026. How we count.

Sites with RTCGnaw code26
Sites with a match in the last 30 days16
New sites in October8
Still serving it when recaptured at least 25 days later8 of 13 sites (62%)
Sites running WordPress (captured since 25 September)65%

Several shop injections lead to the same chain

RTCGnaw is JavaScript planted in compromised WordPress, WooCommerce and Magento assets. Most copies call BNB Smart Chain testnet contracts with selector 0xe2179b8e and converge on the same resolver, controller loader and WebRTC stager.

One WordPress build decodes the contract's ABI string and passes it to eval. A wrapped build accepts ENC: AES-CBC or GZIP: content before calling Function. Obfuscated copies hide the same flow behind hexadecimal strings and XOR decoding. The Magento copy is appended to a cookies.js asset, uses its own JSON-RPC client and turns the Base64 reply into a JavaScript Blob.

A checkout-only WordPress build reads Polygon instead. It sits inline among WooCommerce assets, rewritten to use WP Rocket's delayed-script type, and removes its own element as it starts. It stays quiet for logged-in administrators, sets a page-level __S_INITED flag and waits, through a MutationObserver, for WooCommerce's #payment element. Catalogue, product and cart pages never reach the network stage.

Controllers send each shop its own skimmer

The controller loader holds a list of controller URLs, each a host and a path, as arrays of character codes XORed with a single-byte key. For each controller it sends a JSON POST of {"source": location.pathname + location.search} to the controller's path plus /c and eight random letters and digits, then runs the reply through a JavaScript Blob, Function or an inline script. The BNB Smart Chain builds shuffle the list into a random order first, and fall back to a WebSocket at the same address with the path in a ?source= parameter. Their contracts have listed controllers on systmshield[.]com, threatblck[.]com, defendsstm[.]com, guardsstm[.]com and contentprotectiongate[.]com, under paths such as /wp-includes, /plugins, /cache and /assets.

On 1 November 2026 one contract served a build dressed as Google Tag Manager. It pushes a gtm.start event, then loads /gtag/js from its controller, cdn[.]api-middle-connect[.]com/metrics, with the shop's origin in a cd parameter and the page path in _p, and runs whatever the script leaves in window._gtag_ng.

Three more testnet contracts serve stagers in the same Google Tag Manager dress. The page loader reads the contract with ethers.js, takes the string reply as Base64 and runs it as a Blob script. Two stages keep the XORed controller list: 0xc8ad21434639ecaf8730c4b2b6b178b30e77a307 gives hxxps://tawoauth[.]com/chat and 0xc9233895f25b1135bd0bd352b4b97a7c7210f33b gives hxxps://scomper[.]store/sslsocket.

The third, 0xb5f454d3102f90c876e42bf8d077a16d24ba9d67, hides its strings in a javascript-obfuscator RC4 string array. Its payload is stored reversed and XORed with the key gtag. The stage underneath opens a WebSocket to wss://mouseflow[.]nexus/connection with the page address in source and runs every message it receives with new Function. The hostname copies Mouseflow, a real session-recording service.

The Polygon build gets its list from contract 0x83f3cb2f941773e97069cb0bbaa9028a843f5be1, called with selector 0xd62aa7b8 and the string argument wp. The reply is a JavaScript expression that XORs two arrays with 42 and gives hxxps://woocompay[.]com/assets and hxxps://wpguarding[.]com/system. The contract returned the same pair when we read it on 13 and 29 October 2026. The loader freezes the list with Object.freeze and pins it to navigator.vDataCache with Object.defineProperty, where later page code cannot reconfigure it.

The controllers pick their reply by the request's Origin header. On 29 October 2026 we sent the controller request for two infected shops and received a different build for each: one written for the WooCommerce Stripe gateway, the other for GoDaddy Poynt payments. An origin the controllers do not know gets an empty reply, and so does a request with no Origin.

A fake card form takes the processor's place

Each skimmer build carries a configuration naming the payment container to take over, the processor's own form to hide and the order button. The Stripe build mounts in #wc-stripe-card-element and hides .__PrivateStripeElement, the iframe that holds Stripe's real card fields. The Poynt build mounts in #mwc-payments-poynt-hosted-form and hides #poynt-collect-v2-iframe.

The skimmer runs only when the cookie ak_bmsc_login is absent or set to 1, and never while the WordPress admin bar is on the page. The name copies ak_bmsc, the cookie Akamai's bot manager sets.

It writes its own iframe into the payment container. The frame's document is written in place with no source URL, so it shares the shop's origin and the skimmer reads every keystroke. The card number, expiry and security code inputs use Stripe Elements' default colours, card-brand logos and Stripe's own error messages, such as "Your card number is incomplete.", and the number is checked against brand patterns and the Luhn checksum. A MutationObserver mounts the frame again whenever WooCommerce redraws the payment block.

The Stripe build adds a button styled as Stripe's Link autofill. Pressing it opens a full-screen, Link-branded "403 Forbidden" panel telling the shopper to check the network for viruses or make payment by card.

Card and billing details leave as an image request

A capturing click handler stops the Place order button while the targeted payment method is selected. The skimmer checks the shop's required billing fields, drawing WooCommerce-style notices for empty ones, then its own card fields. It sets ak_bmsc_login to 2 for 15 minutes and collects the card number, expiry, security code and every billing_ field, from name and address to phone and email.

The data goes to the first controller as an image request, with the next controller tried after an error or five seconds. The path after the controller imitates a static image, such as /media/cache/2026-10-29/pixel.gif or /uploads/local/thumb.gif. The query carries t, the time in milliseconds, and p: a JSON record of the shop's hostname, a visitor ID, the field values and the browser's user agent, XORed with the digits of t and Base64-encoded. Anyone holding a logged p and t can decode it.

Then the skimmer shows "The card was declined. Please try again", removes its frame and puts the processor's form back. The shopper pays again on the real form and the order goes through. The cookie keeps the skimmer quiet for 15 minutes, so a reload straight after the decline shows the real form.

A browser data channel carries the next script

The WebRTC stager creates RTCPeerConnection and a data channel, then supplies a hand-built passive SDP answer for a UDP endpoint or TURN relay. One captured build labels the channel with gz: plus the page URL when the browser supports DecompressionStream. It buffers binary chunks until __EOF__, decompresses them when needed and executes the completed script.

Execution can use a new script carrying a nonce copied from an existing page element, Function, or script textContent. Magento-side builds also collect the current values of input, select and textarea elements into window.__OWE_EARLY before opening the channel. One of the controller hosts in the BNB Smart Chain loader's list resolves to 103.141.13[.]26, the UDP endpoint of a stager build.

Sansec published the same passive-SDP, data-channel and copied-nonce structure as a payment-skimmer loader in March 2026. Netskope later documented the BNB Smart Chain testnet and WebRTC combination.

What we see

MonthNew sites
September15
October8
November so far3

Of the 13 sites recaptured at least 25 days after their first match, 8 were still serving RTCGnaw (62%). WordPress runs on 65% of captured sites, and 11 of those 11 WordPress sites run WooCommerce.

Finding RTCGnaw in a store

Inspect deployed JavaScript, checkout traffic and orders for:

  • selector 0xe2179b8e or 0xd62aa7b8, ENC: or GZIP: wrappers, __S_INITED and Object.defineProperty(navigator
  • blockchain RPC calls from checkout, followed by a JSON POST to a path ending in /c and eight random characters, or a /gtag/js request to a host other than Google's with cd and _p parameters
  • RTCPeerConnection with setup:passive, __EOF__, copied script nonces and window.__OWE_EARLY
  • .gif requests from checkout to woocompay[.]com or wpguarding[.]com with p and t in the query
  • an ak_bmsc_login cookie on the shop's domain, and cookies named _ga_, _gid_, _hjid_, _clck_ or _fbp_ followed by six random characters
  • a declined payment followed seconds later by a successful one, where the declined attempt left no record at the payment processor

Compare altered CMS assets with trusted copies, including inline WordPress additions and appended Magento JavaScript. Every shopper whose first attempt was declined in that pattern typed their card and billing details into the skimmer.

Contracts we read

We read 14 RTCGnaw contracts every day.

ChainContractCurrent destinationsLast changed
BNB Smart Chain testnet0x120b843a798d57b7dd785af929a45a93a923820fWebRTC endpoint 185[.]141[.]27[.]56:3479
BNB Smart Chain testnet0x3d4aa83fE2AcEb83A40aE09F03aDdd560aBb5a85Contract 0xb72158bb6642c92ef042aaa681e1ffb8b738ef65
BNB Smart Chain testnet0x419f19712b03eee79db555a3591d55847b4efc4aContract 0x120b843a798d57b7dd785af929a45a93a923820f
BNB Smart Chain testnet0x6874572836dAd9512c308e4831ec35e60a21794BContract 0x79a5dd382ae91aac54393d583816d9ef563d5f73
BNB Smart Chain testnet0x79A5DD382aE91aAc54393d583816D9Ef563D5f73cdn[.]api-middle-connect[.]com
BNB Smart Chain testnet0xAeF2ed8B69eFb5C1B9e75990A5F90D02Eb5f84F8Contract 0xeed9e134ce64bf74be001a942ee3e3cb5c12c999
BNB Smart Chain testnet0xb5f454d3102f90c876e42bf8d077a16d24ba9d67mouseflow[.]nexus
BNB Smart Chain testnet0xB72158BB6642C92eF042AaA681E1Ffb8B738ef65api[.]cloudservicecon[.]com
BNB Smart Chain testnet0xbb0F1C129d6408e60846AEA208D27122f4876dA7cloud[.]protectionflare[.]com
BNB Smart Chain testnet0xc8ad21434639ecaf8730c4b2b6b178b30e77a307tawoauth[.]com
BNB Smart Chain testnet0xc9233895f25b1135bd0bd352b4b97a7c7210f33bscomper[.]store
BNB Smart Chain testnet0xeED9e134CE64BF74bE001A942Ee3e3Cb5C12c999WebRTC endpoint 103[.]141[.]13[.]26:3479
BNB Smart Chain testnet0xfDF6eb935834cB2554341E394194F06395986E02Contract 0xeed9e134ce64bf74be001a942ee3e3cb5c12c999
Polygon0x83f3cb2f941773e97069cb0bbaa9028a843f5be1woocompay[.]com, wpguarding[.]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.