k8s.io/kubernetes@v1.29.3/test/e2e/network/funny_ips.go (about) 1 /* 2 Copyright 2022 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package network 18 19 import ( 20 "context" 21 "fmt" 22 "strconv" 23 "strings" 24 "time" 25 26 "github.com/onsi/ginkgo/v2" 27 28 "k8s.io/kubernetes/test/e2e/framework" 29 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 30 e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output" 31 e2eservice "k8s.io/kubernetes/test/e2e/framework/service" 32 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" 33 "k8s.io/kubernetes/test/e2e/network/common" 34 35 v1 "k8s.io/api/core/v1" 36 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 37 "k8s.io/apimachinery/pkg/util/intstr" 38 "k8s.io/apimachinery/pkg/util/wait" 39 clientset "k8s.io/client-go/kubernetes" 40 admissionapi "k8s.io/pod-security-admission/api" 41 netutils "k8s.io/utils/net" 42 ) 43 44 // What are funny IPs: 45 // The adjective is because of the curl blog that explains the history and the problem of liberal 46 // parsing of IP addresses and the consequences and security risks caused the lack of normalization, 47 // mainly due to the use of different notations to abuse parsers misalignment to bypass filters. 48 // xref: https://daniel.haxx.se/blog/2021/04/19/curl-those-funny-ipv4-addresses/ 49 // 50 // Since golang 1.17, IPv4 addresses with leading zeros are rejected by the standard library. 51 // xref: https://github.com/golang/go/issues/30999 52 // 53 // Because this change on the parsers can cause that previous valid data become invalid, Kubernetes 54 // forked the old parsers allowing leading zeros on IPv4 address to not break the compatibility. 55 // 56 // Kubernetes interprets leading zeros on IPv4 addresses as decimal, users must not rely on parser 57 // alignment to not being impacted by the associated security advisory: CVE-2021-29923 golang 58 // standard library "net" - Improper Input Validation of octal literals in golang 1.16.2 and below 59 // standard library "net" results in indeterminate SSRF & RFI vulnerabilities. xref: 60 // https://nvd.nist.gov/vuln/detail/CVE-2021-29923 61 // 62 // Kubernetes 63 // ip := net.ParseIPSloppy("192.168.0.011") 64 // ip.String() yields 192.168.0.11 65 // 66 // BSD stacks 67 // ping 192.168.0.011 68 // PING 192.168.0.011 (192.168.0.9) 56(84) bytes of data. 69 70 var _ = common.SIGDescribe("CVE-2021-29923", func() { 71 var ( 72 ns string 73 cs clientset.Interface 74 ) 75 76 f := framework.NewDefaultFramework("funny-ips") 77 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline 78 79 ginkgo.BeforeEach(func() { 80 if framework.TestContext.ClusterIsIPv6() { 81 e2eskipper.Skipf("The test doesn't apply to IPv6 addresses, only IPv4 addresses are affected by CVE-2021-29923") 82 } 83 ns = f.Namespace.Name 84 cs = f.ClientSet 85 }) 86 87 /* 88 Create a ClusterIP service with a ClusterIP with leading zeros and check that it is reachable in the IP interpreted as decimal. 89 If the Service is not reachable only FAIL if it is reachable in the IP interpreted as binary, because it will be exposed to CVE-2021-29923. 90 IMPORTANT: CoreDNS since version 1.8.5 discard IPs with leading zeros so Services are not resolvable, and is probably that 91 most of the ecosystem has done the same, however, Kubernetes doesn't impose any restriction, users should migrate their IPs. 92 */ 93 ginkgo.It("IPv4 Service Type ClusterIP with leading zeros should work interpreted as decimal", func(ctx context.Context) { 94 serviceName := "funny-ip" 95 // Use a very uncommon port to reduce the risk of conflicts with other tests that create services. 96 servicePort := 7180 97 jig := e2eservice.NewTestJig(cs, ns, serviceName) 98 99 clusterIPZero, clusterIPOctal := getServiceIPWithLeadingZeros(ctx, cs) 100 if clusterIPZero == "" { 101 e2eskipper.Skipf("Couldn't find a free ClusterIP") 102 } 103 104 ginkgo.By("creating service " + serviceName + " with type=ClusterIP and ip " + clusterIPZero + " in namespace " + ns) 105 _, err := jig.CreateTCPService(ctx, func(svc *v1.Service) { 106 svc.Spec.ClusterIP = clusterIPZero // IP with a leading zero 107 svc.Spec.Type = v1.ServiceTypeClusterIP 108 svc.Spec.Ports = []v1.ServicePort{ 109 {Port: int32(servicePort), Name: "http", Protocol: v1.ProtocolTCP, TargetPort: intstr.FromInt32(9376)}, 110 } 111 }) 112 framework.ExpectNoError(err) 113 114 err = jig.CreateServicePods(ctx, 1) 115 framework.ExpectNoError(err) 116 117 execPod := e2epod.CreateExecPodOrFail(ctx, cs, ns, "execpod", nil) 118 ip := netutils.ParseIPSloppy(clusterIPZero) 119 cmd := fmt.Sprintf("echo hostName | nc -v -t -w 2 %s %v", ip.String(), servicePort) 120 err = wait.PollImmediate(1*time.Second, e2eservice.ServiceReachabilityShortPollTimeout, func() (bool, error) { 121 stdout, err := e2eoutput.RunHostCmd(execPod.Namespace, execPod.Name, cmd) 122 if err != nil { 123 framework.Logf("Service reachability failing with error: %v\nRetrying...", err) 124 return false, nil 125 } 126 trimmed := strings.TrimSpace(stdout) 127 if trimmed != "" { 128 return true, nil 129 } 130 return false, nil 131 }) 132 // Service is working on the expected IP. 133 if err == nil { 134 return 135 } 136 // It may happen that the component implementing Services discard the IP. 137 // We have to check that the Service is not reachable in the address interpreted as decimal. 138 cmd = fmt.Sprintf("echo hostName | nc -v -t -w 2 %s %v", clusterIPOctal, servicePort) 139 err = wait.PollImmediate(1*time.Second, e2eservice.ServiceReachabilityShortPollTimeout, func() (bool, error) { 140 stdout, err := e2eoutput.RunHostCmd(execPod.Namespace, execPod.Name, cmd) 141 if err != nil { 142 framework.Logf("Service reachability failing with error: %v\nRetrying...", err) 143 return false, nil 144 } 145 trimmed := strings.TrimSpace(stdout) 146 if trimmed != "" { 147 return true, nil 148 } 149 return false, nil 150 }) 151 // Ouch, Service has worked on IP interpreted as octal. 152 if err == nil { 153 framework.Failf("WARNING: Your Cluster interprets Service ClusterIP %s as %s, please see https://nvd.nist.gov/vuln/detail/CVE-2021-29923", clusterIPZero, clusterIPOctal) 154 } 155 framework.Logf("Service reachability failing for Service against ClusterIP %s and %s, most probably leading zeros on IPs are not supported by the cluster proxy", clusterIPZero, clusterIPOctal) 156 }) 157 158 }) 159 160 // Try to get a free IP that has different decimal and binary interpretation with leading zeros. 161 // Return both IPs, the one interpretad as binary and the one interpreted as decimal. 162 // Return empty if not IPs are found. 163 func getServiceIPWithLeadingZeros(ctx context.Context, cs clientset.Interface) (string, string) { 164 clusterIPMap := map[string]struct{}{} 165 var clusterIPPrefix string 166 // Dump all the IPs and look for the ones we want. 167 list, err := cs.CoreV1().Services(metav1.NamespaceAll).List(ctx, metav1.ListOptions{}) 168 framework.ExpectNoError(err) 169 for _, svc := range list.Items { 170 if len(svc.Spec.ClusterIP) == 0 || svc.Spec.ClusterIP == v1.ClusterIPNone { 171 continue 172 } 173 // Get the clusterIP prefix, needed later for building the IPs with octal format. 174 if clusterIPPrefix == "" { 175 // split the IP by "." 176 // get the first 3 values 177 // join them again using dots, dropping the last field 178 clusterIPPrefix = strings.Join(strings.Split(svc.Spec.ClusterIP, ".")[:3], ".") 179 } 180 // Canonicalize IP. 181 ip := netutils.ParseIPSloppy(svc.Spec.ClusterIP) 182 clusterIPMap[ip.String()] = struct{}{} 183 } 184 185 // Try to get a free IP between x.x.x.11 and x.x.x.31, this assumes a /27 Service IP range that should be safe. 186 for i := 11; i < 31; i++ { 187 ip := fmt.Sprintf("%s.%d", clusterIPPrefix, i) 188 // Check the IP in decimal format is free. 189 if _, ok := clusterIPMap[ip]; !ok { 190 o, err := strconv.ParseInt(fmt.Sprintf("%d", i), 8, 64) 191 // Omit ips without binary representation i.e. x.x.x.018, x.x.x.019 ... 192 if err != nil { 193 continue 194 } 195 ipOctal := fmt.Sprintf("%s.%d", clusterIPPrefix, o) 196 // Check the same IP in octal format is free. 197 if _, ok := clusterIPMap[ipOctal]; !ok { 198 // Add a leading zero to the decimal format. 199 return fmt.Sprintf("%s.0%d", clusterIPPrefix, i), ipOctal 200 } 201 } 202 } 203 return "", "" 204 }