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:
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):
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:
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/.