Istio Security Testing: mTLS Policy Bypass, Envoy Sidecar Exploitation, and Control Plane Attacks

Istio is the most widely deployed service mesh, providing mTLS, fine-grained authorization policies, JWT validation, and traffic management via Envoy proxies. Its security model depends on correct PeerAuthentication (mTLS enforcement), AuthorizationPolicy (RBAC), and RequestAuthentication (JWT) configuration. Critical issues include PeerAuthentication in PERMISSIVE mode (allows plaintext from uninjected pods), AuthorizationPolicy with missing or overly broad ALLOW rules, Envoy admin interface exposure on port 15000 inside pods, istiod API accessible to unauthorized clients, and egress control bypass. This guide covers systematic Istio security assessment.

Table of Contents

  1. PeerAuthentication PERMISSIVE Mode Bypass
  2. AuthorizationPolicy Gap Testing
  3. Envoy Admin Interface Exploitation
  4. istiod Control Plane Attack Surface
  5. RequestAuthentication JWT Policy Bypass
  6. Egress Gateway Bypass
  7. Istio Security Hardening

PeerAuthentication PERMISSIVE Mode Bypass

# PeerAuthentication controls mTLS enforcement mode:
# STRICT = only mTLS connections accepted (secure)
# PERMISSIVE = both mTLS and plaintext accepted (insecure default)
# DISABLE = only plaintext (no mTLS)

# Check PeerAuthentication policies
kubectl get peerauthentication -A 2>/dev/null
kubectl get peerauthentication -n istio-system default -o json 2>/dev/null | python3 -c "
import json,sys
r = json.load(sys.stdin)
spec = r.get('spec',{})
mtls = spec.get('mtls',{}).get('mode','NOT SET')
print(f'Global mTLS mode: {mtls}')
# PERMISSIVE or NOT SET = plaintext allowed from uninjected sources
"

# Check namespace-level overrides
kubectl get peerauthentication -n production 2>/dev/null
# If none exists: inherits global policy

# Test: from an uninjected pod, reach an injected service
kubectl run test-bypass --image=curlimages/curl --restart=Never \
  --annotations='sidecar.istio.io/inject=false' \
  -n production -- sleep 3600

kubectl exec test-bypass -n production -- \
  curl -s http://payment-service.production.svc.cluster.local/health 2>/dev/null
# If returns data AND PeerAuthentication is PERMISSIVE:
# Plaintext access confirmed — uninjected pods bypass mTLS entirely

AuthorizationPolicy Gap Testing

# AuthorizationPolicy controls which services/identities can communicate
# Default: no AuthorizationPolicy = ALLOW ALL (deny-all requires explicit policy)

# Check if a default deny-all policy exists
kubectl get authorizationpolicy -n istio-system default 2>/dev/null
kubectl get authorizationpolicy -n production 2>/dev/null
# If no deny-all policy: all service-to-service traffic is permitted

# Audit AuthorizationPolicy for overly broad ALLOW rules
kubectl get authorizationpolicies -A -o json 2>/dev/null | python3 -c "
import json,sys
r = json.load(sys.stdin)
for p in r.get('items',[]):
    ns = p['metadata']['namespace']
    name = p['metadata']['name']
    spec = p.get('spec',{})
    for rule in spec.get('rules',[]):
        from_field = rule.get('from',[])
        for src in from_field:
            principals = src.get('source',{}).get('principals',[])
            if '*' in principals or not principals:
                print(f'BROAD ALLOW: {ns}/{name} — principals: {principals}')
"

# Test: access a restricted service from an unauthorized service account
# (assumes you've compromised a pod in a different namespace)
kubectl exec -n attacker-ns attacker-pod -- \
  curl -s http://admin-service.production.svc.cluster.local/users 2>/dev/null
# Should return RBAC: access denied if policies are correct

Envoy Admin Interface Exploitation

# Envoy admin interface runs on port 15000 inside each pod
# If accessible from other pods, it leaks config and certificates

# From inside the same pod (always accessible):
kubectl exec -n production payment-pod -c istio-proxy -- \
  curl -s localhost:15000/config_dump 2>/dev/null | python3 -c "
import json,sys
r = json.load(sys.stdin)
# Look for cluster TLS context (contains certificate data)
for config in r.get('configs',[]):
    if 'static_secrets' in str(config):
        print('Found secret data in Envoy config')
"

# Check if Envoy admin port 15000 is accessible from OTHER pods
kubectl exec -n attacker-ns attacker-pod -- \
  curl -s http://VICTIM-POD-IP:15000/stats 2>/dev/null | head -20
# If returns stats: Envoy admin is network-accessible — critical

# Envoy admin has dangerous endpoints:
# /quitquitquit — kills the Envoy process (DoS)
# /reset_counters — resets metrics
# /logging?level=debug — enables debug logging (may log secrets)
# /config_dump — dumps full Envoy configuration including TLS certs

# Extract certificate data from Envoy admin
kubectl exec -n production payment-pod -c istio-proxy -- \
  curl -s localhost:15000/config_dump 2>/dev/null | \
  python3 -c "
import json,sys,re
data = sys.stdin.read()
# Find certificate/key patterns
certs = re.findall(r'-----BEGIN [^-]+ CERTIFICATE-----.*?-----END [^-]+ CERTIFICATE-----', data, re.DOTALL)
for cert in certs[:3]:
    print(cert[:200])
"

Istio Security Hardening

Istio Security Hardening Checklist:
Security TestMethodRisk
PeerAuthentication in PERMISSIVE modeCheck global PeerAuthentication; test from uninjected podHigh
No default deny-all AuthorizationPolicyMissing deny-all means all service traffic allowedHigh
Envoy admin port 15000 network-accessiblecurl VICTIM-IP:15000/config_dump from another podHigh
istiod debug endpoint accessiblecurl istiod:15014/debug/config_dumpMedium
Egress not controlledcurl from pod to external IP — should be blockedMedium
JWT RequestAuthentication with missing pathsAccess paths not covered by RequestAuthentication rulesMedium

Automate Istio Service Mesh Security Testing

Ironimo tests Istio deployments for PeerAuthentication PERMISSIVE mode bypass, AuthorizationPolicy coverage gaps, Envoy admin interface exposure, istiod control plane attack surface, JWT validation bypass, egress gateway escape, and ambient mesh security — covering the complete Istio attack surface.

Start free scan