b0b0haha opened a new issue, #2383: URL: https://github.com/apache/apisix-ingress-controller/issues/2383
### Current Behavior Target Apache APISIX ingress controller (https://github.com/apache/apisix-ingress-controller) Reporting channel: [email protected] (https://security.apache.org/projects/) Affected Versions Latest version 019d719ef421e97ab47e68c0f34724010b05f1e0 Exploitation Prerequisites To exploit this vulnerability, an attacker needs: 1. Control over the ApisixResourceVersion parameter: 1. The attacker needs to control s.opts.ApisixResourceVersion, which is used in the replacement 2. This is the critical input that would contain the malicious payload with newlines 2. Access to the test scaffold environment: 1. This vulnerability exists in the test/e2e package, suggesting this is test code 2. An attacker would need to: - Access the testing environment - Find a way to trigger the tests with controlled parameters - Or find similar code patterns in the production code 3. Kubernetes API access: 1. For the injected Pod (or other resources) to be created, the test scaffold must have permissions to create such resources in the target Kubernetes cluster 2. The service account running the tests needs sufficient RBAC permissions Exploitation Impact By controlling s.opts.ApisixResourceVersion to inject malicious YAML fragments, it's possible to create arbitrary resources that could lead to privilege escalation. Root Cause Analysis In test\e2e\scaffold\consumer.go: The call site of s.replaceApiVersion: ``` func (s *Scaffold) CreateVersionedApisixResource(yml string) error { kindValue := s.getKindValue(yml) if _, ok := createVersionedApisixResourceMap[kindValue]; ok { // Validation done here resource := s.replaceApiVersion(yml, s.opts.ApisixResourceVersion) // Direct replacement to another resource return s.CreateResourceFromString(resource) } return fmt.Errorf("the resource %s does not support", kindValue) } func (s *Scaffold) ApisixConsumerBasicAuthCreated(name, username, password string) error { ac := fmt.Sprintf(_apisixConsumerBasicAuth, s.opts.ApisixResourceVersion, name, username, password) return s.CreateVersionedApisixResource(ac) } _apisixConsumerBasicAuth = ` apiVersion: %s kind: ApisixConsumer metadata: name: %s spec: authParameter: basicAuth: value: username: %s password: %s ` ``` In test\e2e\scaffold\scaffold.go: ``` var ( versionRegex = regexp.MustCompile( apiVersion: apisix.apache.org/v.*?\n ) kindRegex = regexp.MustCompile( kind: (.*?)\n ) ) func (s *Scaffold) replaceApiVersion(yml, ver string) string { return versionRegex.ReplaceAllString(yml, "apiVersion: "+ver+"\n") } ``` In test\e2e\scaffold\k8s.go: ``` CreateResourceFromString calls k8s.KubectlApplyFromStringE, the function finally uses kubectl to apply the resouce yaml. func (s *Scaffold) CreateResourceFromString(yaml string) error { err := k8s.KubectlApplyFromStringE(s.t, s.kubectlOptions, yaml) // if the error raised, it may be a &shell.ErrWithCmdOutput, which is useless in debug if err != nil { err = fmt.Errorf(err.Error()) } return err } func KubectlApplyFromStringE(t testing.TestingT, options *KubectlOptions, configData string) error { tmpfile, err := StoreConfigToTempFileE(t, configData) if err != nil { return err } defer os.Remove(tmpfile) return KubectlApplyE(t, options, tmpfile) } func KubectlApplyE(t testing.TestingT, options *KubectlOptions, configPath string) error { return RunKubectlE(t, options, "apply", "-f", configPath) } func RunKubectlE(t testing.TestingT, options *KubectlOptions, args ...string) error { _, err := RunKubectlAndGetOutputE(t, options, args...) return err } func RunKubectlAndGetOutputE(t testing.TestingT, options *KubectlOptions, args ...string) (string, error) { cmdArgs := []string{} if options.ContextName != "" { cmdArgs = append(cmdArgs, "--context", options.ContextName) } if options.ConfigPath != "" { cmdArgs = append(cmdArgs, "--kubeconfig", options.ConfigPath) } if options.Namespace != "" { cmdArgs = append(cmdArgs, "--namespace", options.Namespace) } cmdArgs = append(cmdArgs, args...) command := shell.Command{ Command: "kubectl", Args: cmdArgs, Env: options.Env, } return shell.RunCommandAndGetOutputE(t, command) } ``` The vulnerability has two main causes: 1. Regular Expression Matching Flaw The original regex apiVersion: apisix.apache.org/v.*?\n with .*? allows matching any characters (including spaces before the newline), but doesn't filter the ver input: ``` // Regex allows version numbers to contain newlines versionRegex.ReplaceAllString(yml, "apiVersion: "+ver+"\n") ``` 2. If ver contains \n, it directly breaks the YAML structure. For example, if an attacker sets s.opts.ApisixResourceVersion to: "v2\nkind: Pod\nspec:\n containers:\n - name: evil-container\n image: evil/rootkit" 1The replaced yaml content becomes: ``` - apiVersion: v2 kind: Pod spec: containers: name: evil-container image: evil/rootkit metadata: name: %s spec: authParameter:... ``` 1. At this point, Kubernetes will prioritize parsing kind: Pod rather than the original ApisixConsumer, resulting in the creation of a malicious Pod. 2. Dynamic Resource Creation Logic Flaw The CreateVersionedApisixResource method only validates the original kind field without performing secondary validation on the replaced content: ``` // Original logic only checks the original kind type if _, ok := createVersionedApisixResourceMap[kindValue]; ok { ... } ``` ### Expected Behavior There should be sanitizer for s.opts.ApisixResourceVersion ### Error Logs _No response_ ### Steps to Reproduce Exploitation Process The most likely attack path is: 1. Inject the following payload into s.opts.ApisixResourceVersion: v2\nkind: Pod\nspec:\n containers:\n - name: evil-container\n image: evil/rootkit\n hostNetwork: true\n# 2. When ApisixConsumerBasicAuthCreated or another function using CreateVersionedApisixResource is called, the malicious YAML will be created 3. Instead of creating an ApisixConsumer resource, a Pod with elevated privileges will be created, potentially allowing the attacker to compromise the cluster ### Environment - APISIX Ingress controller version (run `apisix-ingress-controller version --long`) - Kubernetes cluster version (run `kubectl version`) - OS version if running APISIX Ingress controller in a bare-metal environment (run `uname -a`) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
