Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book
CVE-2021-43557: Apache APISIX: Path traversal poprzez zmienną $request_uri
W tym artykule opisuję niebezpieczne użycie zmiennej $request_uri w Apache APISIX ingress controller. Moja praca zaowocowała zgłoszeniem błędu bezpieczeństwa, który otrzymał numer CVE-2021-43557.
Zacznijmy od tego, czym jest Apache APISIX:
„… to dynamiczna brama API (ang. API gateway) o wysokiej wydajności. Ma ona duże możliwości konfiguracji w zakresie kształtowania ruchu, takie jak: load balancing, dynamiczne upstreamy, wdrożenia canary, circuit breaking, uwierzytelnienie, monitorowanie i wiele więcej”.
Oprócz tego warto wiedzieć, że działa on na bazie nginx.
Zastanówmy się w takim razie, czym jest zmienna $request_uri i jaka jest jej rola. Dokumentacja nginx mówi krótko:
pełny oryginalny uri (wraz z argumentami).
To, co jest powiedziane nie wprost, to informacja, że zmienna ta nie jest znormalizowana. Może bowiem zawierać ciągi znaków wpływających na ścieżkę, np. ‘../’. Jest to o tyle ciekawa sprawa, że nginx pozwala na takie rzeczy:
curl http://nie_istnieje/../o_ten_istnieje/endpoint
$request_uri: /nie_istnieje/../o_ten_istnieje/endpoint
$uri: /o_ten_istnieje/endpoint
Fragment uri: “/nie_istnieje/” zostaje pominięty przez nginx. Całe zapytanie zostało wykonane poprawnie. Serwer przetwarza tylko to, co jest w zmiennej $uri. Jeżeli w tym przypadku $request_uri jest zmienną w „podprocesie” wykonującym uwierzytelnienie i autoryzację, wówczas może istnieć szansa na oszukanie niczego nie spodziewającego się dewelopera. Tak właśnie było w przypadku Apache APISIX i CVE-2021-43557.
W analizowanym ingress kontrolerze mamy do dyspozycji plugin o nazwie uri-blocker. Umożliwia on zablokowanie dostępu do zdefiniowanej przez wyrażenie regularne ścieżki. Jak już możecie się domyślać, spróbuję obejść to zabezpieczenie, używając podatności path traversal.
Testowanie w minikube
Najpierw instalujemy minikube, a potem Apache APISIX za pomocą narzędzia helm, używając Chart w wersji 0.7.2 (APISIX v2.10.0):
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"]
Zagłębmy się w jej najważniejsze aspekty:
- skonfigurowane są ścieżki dla “public-service” i “protected-service”;
- skonfigurowany jest plugin “proxy-rewrite” w celu usunięcia prefiksów (upstreamy będą dostawać zapytania bez prefiksów);
- włączony jest uri-blocker dla “protected-service”, tak, aby blokować wszystkie zapytania do niego; może to wydawać się błędne, ale jest najprostszym sposobem na przetestowanie podatności.
Weryfikacja podatności
W celu uproszczenia wywołania zapytań stworzyłem prosty skrypt, który pomoże mi w środowisku minikube. Zapisałem go do pliku:
apisix_request.sh
kubectl exec -it -n ${namespace of Apache APISIX} ${Pod name of Apache APISIX} -- curl --path-as-is http://127.0.0.1:9080/public-service/public -H 'Host: app.test'
Zacznijmy od sprawdzenia, czy wszystko działa tak, jak powinno:
$ ./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>
Analiza przyczyny
Plugin uri-blocker używa zmiennej ctx.var.request_uri, nie normalizując jej wcześniej. Jest to widoczne w tym fragmencie kodu:
Bezpośrednią konsekwencją tej podatności jest możliwość obejścia zabezpieczeń bazujących na uri-blocker. Dodatkowo podatne mogą być samodzielnie stworzone integracje bazujące na ctx.var.request_uri.
Co można zrobić, żeby się przed tym ochronić? W przypadku samego Apache APISIX – dokonać aktualizacji do wersji 2.10.2, a w przypadku użycia ctx.var.request_uri – znormalizować przed użyciem.
Po więcej materiałów związanych z tym zagadnieniem, w tym wyniki badania innych ingress kontrolerów, zapraszam na mój blog: https://xvnpw.github.io/.
Kod źródłowy do tego artykułu możecie znaleźć tutaj: https://github.com/xvnpw/k8s-CVE-2021-43557-poc
Ps. Swoją profesjonalną przygodę z bezpieczeństwem zacząłem kilka lat temu od szkolenia z bezpieczeństwa web API prowadzonego przez Securitum. Serdecznie polecam wszystkim to i inne prowadzone przez nich szkolenia, są naprawdę na wysokim poziomie :-)
~Marcin Niemiec