NordVPN – zabawy z API i Cloudflare X-Forwarder-For
Napisał: Patryk Krawaczyński
07/01/2021 w Pen Test Brak komentarzy. (artykuł nr 765, ilość słów: 802)
S
erwery API serwisu NordVPN stoją za Cloudflare. Jeśli wejdziemy na jeden z adresów API otrzymamy w formacie JSON geo informację o swoim adresie IP z zaznaczeniem czy jest on chroniony przez wspomnianą usługę, czy nie: "protected": false
. Jeśli z ciekawości do metody insights dodamy parametr ?ip=$IP
okazuje się, że możemy taką informację otrzymać o dowolnym (poprawnym) adresie IP w internecie. Na przykład: 1.1.1.1. W praktyce oznacza to, że oprócz VPN możemy gratis dostać usługę GeoIP znaną z Maxmind. W celach testowych zebrałem sobie 1000 adresów IP, dla których chciałbym sprawdzić informacje geo. Skoro północny serwis mi to oferuje za darmo to wystarczy uruchomić prosty skrypt:
for i in `cat ips` do curl https://api.nordvpn.com/v1/helpers/ips/insights?ip="$i" -w "\n" done
Niestety po około ~500 żądaniach nasz skrypt wpadł w mechanizm limitowania żądań i zamiast wyników zaczęła ukazywać się odpowiedź:
<html> <head><title>429 Too Many Requests</title></head> <body bgcolor="white"> <center><h1>429 Too Many Requests</h1></center> <hr><center>nginx</center> </body> </html>
Mimo, że setki żądań to i tak duże okno, jak na taki endpoint to czekanie aż limit z naszego źródłowego adresu IP zostanie zdjęty jest średnim czasowo rozwiązaniem. Cloudflare jako WAF (ang. Web Application Firewall) pełni również rolę serwerów proxy. Serwery te zazwyczaj dla ruchu HTTP* informację o źródłowych adresach IP przekazują za pomocą nagłówka X-Forwarded-For (któremu nie można do końca ufać jeśli się go odpowiednio nie traktuje przez serwery proxy). Jeśli odwiedzam adres serwisu, który jest chroniony przez Cloudflare i nagłówek X-Forwarded-For
nie jest ustawiony w żądaniu to jest on skonstruowany na podstawie mojego źródłowego adresu IP i przesyłany dalej serwerowi końcowemu. Jeśli wysyłam żądanie z już ustawionym nagłówkiem X-Forwarded-For
to mój źródłowy adres IP zostanie ustawiony jako pierwszy, a wartości z istniejącego nagłówka jako kolejne po przecinku (przynajmniej według dokumentacji). Załóżmy, że mój adres IP to: 11.22.33.44. Wysyłam żądanie HTTP bez ustawionego nagłówka X-Forwarded-For
. Serwer za Cloudflare otrzymuje wartość nagłówka:
X-Forwarded-For: 11.22.33.44
Jeśli wysyłam żądanie HTTP z ustawionym nagłówkiem X-Forwarder-For
na wartość: 55.66.77.88 to serwer według dokumentacji usługi Cloudflare powinien otrzymać na swoim poziomie nagłówek:
X-Forwarded-For: 11.22.33.44,55.66.77.88
Czyli na pierwszym miejscu zawsze powinien być “rzeczywisty” adres źródłowy. Niestety rzeczywistość nie jest już taka piękna. To, co dociera do serwera wygląda tak:
X-Forwarded-For: 55.66.77.88,11.22.33.44
Co to zmienia w naszej sytuacji? Otóż każdy klient Cloudflare, który oparł się na ustalaniu adresu IP klienta na podstawie nagłówka X-Forwarded-For
zgodnie z dokumentacją – robi to dobrze tylko w przypadku, gdy nagłówek taki nie istnieje. W innym przypadku istnieje możliwość sfałszowania adresu źródłowego, który dociera do serwera za usługą Cloudflare (możemy zatruć wpisy w logach lub oszukać mechanizmy dostępu opierające się na IP). Sprawdźmy to na przykładzie API NordVPN:
curl -s https://api.nordvpn.com/user/address XX.230.161.31 curl -s -H "X-Forwarded-For: 1.1.1.1" https://api.nordvpn.com/user/address 1.1.1.1
Dla pewności sprawdźmy jeszcze, czy nabierzemy NordVPN mówiąc mu, że korzystamy z jego serwera VPN w Polsce:
curl -H "X-Forwarded-For: X.XXX.206.59" https://api.nordvpn.com/v1/helpers/ips/insights {"ip":"X.XXX.206.59","country":"Poland","country_code":"PL","city":"Warsaw", "isp":"M247 Ltd","protected":true,"longitude":20.9999,"latitude":52.1532, "state_code":"14","zip_code":"02-822"}
Wróćmy jeszcze do pierwszej wersji skryptu, który wpadł w limitowanie żądań z jednego adresu IP. Skoro mogę dowolnie manipulować adresem źródłowym, który widzi serwis za Cloudflare robiąc lekką modyfikację mogę ominąć mechanizm typu rate limit przekazując za każdym razem inną wartość nagłówka X-Forwarded-For
. Od strony serwera końcowego będzie to miało postać, że unikalny adres IP robi jedno żądanie – w rzeczywistości to jeden i ten sam adres robi wiele żądań:
for i in `cat ips` do curl -H "X-Forwarded-For: $i" https://api.nordvpn.com/v1/helpers/ips/insights" \ -w "\n" done
{"ip":"XX.230.0.0","country":"Poland","country_code":"PL"... {"ip":"XX.230.0.1","country":"Poland","country_code":"PL"... {"ip":"XX.230.0.2","country":"Poland","country_code":"PL"... {"ip":"XX.230.0.3","country":"Poland","country_code":"PL"... {"ip":"XX.230.0.4","country":"Poland","country_code":"PL"... {"ip":"XX.230.0.5","country":"Poland","country_code":"PL"... {"ip":"XX.230.0.6","country":"Poland","country_code":"PL"...
Bez problemu skrypt sprawdził 1000 różnych adresów IP bez wyzwalania mechanizmu limitującego.
Cloudflare Bug Bounty rewarded you with a bounty of $XXX for Incorrect handling of an existing X-Forwarded-For header
Rozjazd pomiędzy dokumentacją, a rzeczywistą sytuacją, która mogła prowadzić do niepoprawnych implementacji nagłówka XFF został zgłoszony do Cloudflare 31 grudnia 2020, a rozwiązany 5 stycznia 2021. Problem z niepoprawnym traktowaniem nagłówka XFF przez API został zgłoszony do NordVPN 29 grudnia 2020 roku – nie został zakwalifikowany jako problem bezpieczeństwa, ponieważ w odpowiedzi z 6 stycznia 2021 została przedstawiona informacja, że błąd występuje tylko w danych zwracanych przez aplikację API – logi serwerów posiadają wpisy o prawdziwym adresie IP. Niestety po zgłoszeniu przestały działać niektóre punkty końcowe API… np. user/address
.
Podsumowanie:
Jeśli Twój serwis znajduje się za opisanym WAFem i korzysta z XFF warto zweryfikować swoją konfigurację nagłówków. Jak zaleca sam Cloudflare najlepiej wykorzystać nagłówki CF-Connecting-IP lub True-Client-IP, które posiadają spójny format. Jeśli sami posiadamy serwery proxy, które przekazują adres IP w nagłówku XFF – i pozwalamy na zawartość przekazywaną przez klienta – warto zastanowić się nad ich usuwaniem i budowaniu od nowa tylko na podstawie źródłowego adresu IP.
Więcej informacji: How does Cloudflare handle HTTP Request headers?, nord.api, nordvpn-server-find, nordtoy, How to use public NordVPN API