Skip to content

Spoofing of X-Forwarded-Host header is possible during redirect to HTTPS #13158

Open
@617m4rc

Description

@617m4rc

What happened:

It is possible to spoof the X-Forwarded-Host header during the HTTP to HTTPS redirect in Ingress NGINX. This may allow an attacker to manipulate the redirect behavior by providing a malicious or incorrect X-Forwarded-Host value.

What you expected to happen:

Ingress NGINX should properly handle the X-Forwarded-Host header and prevent it from being spoofed. X-Forwarded headers should only be considered safe from the CIDR ranges defined in property proxy-real-ip-cidr.

NGINX Ingress controller version (exec into the pod and run /nginx-ingress-controller --version):

-------------------------------------------------------------------------------
NGINX Ingress controller
  Release:       v1.12.1
  Build:         51c2b819690bbf1709b844dbf321a9acf6eda5a7
  Repository:    https://github.com/kubernetes/ingress-nginx
  nginx version: nginx/1.25.5

-------------------------------------------------------------------------------

Kubernetes version (use kubectl version): 1.32

Environment:

  • Cloud provider or hardware configuration: AWS EKS
  • OS (e.g. from /etc/os-release): Bottlerocket
  • Kernel (e.g. uname -a): Linux ingress-nginx-gh-controller-649f86695b-7bsm2 6.1.124 #1 SMP PREEMPT_DYNAMIC Sat Jan 25 00:17:27 UTC 2025 x86_64 Linux
  • Install tools:
    • Cluster deployed via Terraform
  • Basic cluster related info:

Client Version: v1.32.3 Kustomize Version: v5.5.0 Server Version: v1.32.2-eks-bc803b4

NAME                                                  STATUS   ROLES    AGE   VERSION               INTERNAL-IP    EXTERNAL-IP   OS-IMAGE                                KERNEL-VERSION                  CONTAINER-RUNTIME
xxx   Ready    <none>   9d    v1.32.0-eks-5ca49cb   10.0.204.226   <none>        Amazon Linux 2                          5.10.234-225.910.amzn2.x86_64   containerd://1.7.25
xxx     Ready    <none>   11d   v1.32.0-eks-5ca49cb   10.0.74.3      <none>        Amazon Linux 2                          5.10.234-225.910.amzn2.x86_64   containerd://1.7.25
xxx           Ready    <none>   11h   v1.32.0-eks-2e66e76   10.0.101.104   <none>        Bottlerocket OS 1.32.0 (aws-k8s-1.32)   6.1.124                         containerd://1.7.24+bottlerocket
xxx          Ready    <none>   11h   v1.32.0-eks-2e66e76   10.0.102.164   <none>        Bottlerocket OS 1.32.0 (aws-k8s-1.32)   6.1.124                         containerd://1.7.24+bottlerocket
xxx          Ready    <none>   11h   v1.32.0-eks-2e66e76   10.0.109.145   <none>        Bottlerocket OS 1.32.0 (aws-k8s-1.32)   6.1.124                         containerd://1.7.24+bottlerocket
xxx           Ready    <none>   11h   v1.32.0-eks-2e66e76   10.0.116.60    <none>        Bottlerocket OS 1.32.0 (aws-k8s-1.32)   6.1.124                         containerd://1.7.24+bottlerocket
xxx           Ready    <none>   11h   v1.32.0-eks-2e66e76   10.0.153.23    <none>        Bottlerocket OS 1.32.0 (aws-k8s-1.32)   6.1.124                         containerd://1.7.24+bottlerocket
xxx          Ready    <none>   11h   v1.32.0-eks-2e66e76   10.0.172.111   <none>        Bottlerocket OS 1.32.0 (aws-k8s-1.32)   6.1.124                         containerd://1.7.24+bottlerocket
xxx           Ready    <none>   11h   v1.32.0-eks-2e66e76   10.0.172.44    <none>        Bottlerocket OS 1.32.0 (aws-k8s-1.32)   6.1.124                         containerd://1.7.24+bottlerocket
xxx           Ready    <none>   11h   v1.32.0-eks-2e66e76   10.0.177.173   <none>        Bottlerocket OS 1.32.0 (aws-k8s-1.32)   6.1.124                         containerd://1.7.24+bottlerocket
xxx          Ready    <none>   11h   v1.32.0-eks-2e66e76   10.0.189.164   <none>        Bottlerocket OS 1.32.0 (aws-k8s-1.32)   6.1.124                         containerd://1.7.24+bottlerocket
xxx           Ready    <none>   11h   v1.32.0-eks-2e66e76   10.0.198.209   <none>        Bottlerocket OS 1.32.0 (aws-k8s-1.32)   6.1.124                         containerd://1.7.24+bottlerocket
xxx            Ready    <none>   11h   v1.32.0-eks-2e66e76   10.0.211.8     <none>        Bottlerocket OS 1.32.0 (aws-k8s-1.32)   6.1.124                         containerd://1.7.24+bottlerocket
xxx          Ready    <none>   11h   v1.32.0-eks-2e66e76   10.0.227.230   <none>        Bottlerocket OS 1.32.0 (aws-k8s-1.32)   6.1.124                         containerd://1.7.24+bottlerocket
xxx          Ready    <none>   11h   v1.32.0-eks-2e66e76   10.0.246.197   <none>        Bottlerocket OS 1.32.0 (aws-k8s-1.32)   6.1.124                         containerd://1.7.24+bottlerocket
xxx            Ready    <none>   11h   v1.32.0-eks-2e66e76   10.0.253.65    <none>        Bottlerocket OS 1.32.0 (aws-k8s-1.32)   6.1.124                         containerd://1.7.24+bottlerocket
xxx           Ready    <none>   11h   v1.32.0-eks-2e66e76   10.0.94.237    <none>        Bottlerocket OS 1.32.0 (aws-k8s-1.32)   6.1.124                         containerd://1.7.24+bottlerocket
  • How was the ingress-nginx-controller installed:

    • If helm was used then please show output of helm ls -A | grep -i ingress

ingress-nginx xxx 7 2025-03-06 05:24:31.654774386 +0000 UTC deployed ingress-nginx-4.12.0 1.12.0

  • If helm was used then please show output of helm -n <ingresscontrollernamespace> get values <helmreleasename>
USER-SUPPLIED VALUES:
controller:
  allowSnippetAnnotations: false
  config:
    enable-ocsp: true
    enable-owasp-modsecurity-crs: "true"
    enable-real-ip: true
    generate-request-id: true
    hsts-max-age: "31536000"
    http-snippet: ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
    proxy-connect-timeout: "60"
    proxy-read-timeout: "1800"
    proxy-real-ip-cidr: 10.0.0.0/16
    proxy-send-timeout: "1800"
    real-ip-recursive: "on"
    ssl-ciphers: ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384
    ssl-protocols: TLSv1.2 TLSv1.3
    ssl-session-cache: "true"
    ssl-session-cache-size: 10m
    upstream-keepalive-requests: "1000"
    upstream-keepalive-timeout: "55"
    use-forwarded-headers: "true"
    use-http2: true
    use-proxy-protocol: "true"
    worker-shutdown-timeout: 60s
  service:
    annotations:
      service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval: "60"
      service.beta.kubernetes.io/aws-load-balancer-access-log-enabled: "true"
      service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name: xxx
      service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags: xxx
      service.beta.kubernetes.io/aws-load-balancer-attributes: dns_record.client_routing_policy=availability_zone_affinity
      service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp
      service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "false"
      service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: instance
      service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: '*'
      service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
      service.beta.kubernetes.io/aws-load-balancer-type: nlb-ip
    externalTrafficPolicy: Local
  ...
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: xxx
    nginx.ingress.kubernetes.io/backend-protocol: HTTP
    nginx.ingress.kubernetes.io/enable-opentracing: "false"
    nginx.ingress.kubernetes.io/proxy-body-size: 0m
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.ingress.kubernetes.io/service-upstream: "true"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
  creationTimestamp: "2025-02-25T08:32:21Z"
  generation: 1
  name: ingress
  namespace: xxx
spec:
  rules:
  - host: xxx.mysaas.com
    http:
      paths:
      - backend:
          service:
            name: xxx
            port:
              number: 80
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - xxx.mysaas.com
    secretName: tls-secret
status:
  loadBalancer:
    ingress:
    - hostname: xxx

How to reproduce this issue:

  • Install Ingress NGINX via Helm chart and set parameter proxy-real-ip-cidr.
  • Create an ingress with annotation nginx.ingress.kubernetes.io/ssl-redirect: "true"
  • make a request with spoofed X-Forwarded-Host header

Note that the request needs to be via HTTP and not HTTPS to reproduce the redirect.

curl --path-as-is -i -s -k -v -X $'GET' -H $'Host: xxx.mysaas.com' -H $'X-Forwarded-Host: kubernetes.github.io/ingress-nginx' $'http://xxx.mysaas.com/'

Result:

HTTP/1.1 308 Permanent Redirect
Date: Fri, 04 Apr 2025 15:45:46 GMT
Content-Type: text/html
Content-Length: 164
Connection: keep-alive
Location: https://kubernetes.github.io/ingress-nginx

<html>
<head><title>308 Permanent Redirect</title></head>
<body>
<center><h1>308 Permanent Redirect</h1></center>
<hr><center>nginx</center>
</body>
</html>

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/bugCategorizes issue or PR as related to a bug.lifecycle/frozenIndicates that an issue or PR should not be auto-closed due to staleness.needs-priorityneeds-triageIndicates an issue or PR lacks a `triage/foo` label and requires one.

    Type

    No type

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions