-15% na nową książkę sekuraka: Wprowadzenie do bezpieczeństwa IT. Przy zamówieniu podaj kod: 10000

CVE-2021-43557: Apache APISIX: Path traversal poprzez zmienną $request_uri

01 grudnia 2021, 14:52 | W biegu | 0 komentarzy

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

Spodobał Ci się wpis? Podziel się nim ze znajomymi:



Komentarze

Odpowiedz