Skip to content

Instantly share code, notes, and snippets.

@marcodebe
Last active June 8, 2017 13:34
Show Gist options
  • Save marcodebe/f893c1850a4d7bc0f9a7a2c1a0a4b9ce to your computer and use it in GitHub Desktop.
Save marcodebe/f893c1850a4d7bc0f9a7a2c1a0a4b9ce to your computer and use it in GitHub Desktop.
Ceci n'est pas une pipe.

Indice

Filosofia Unix

  • Ogni programma deve svolgere un compito solo, e deve farlo bene.
  • Ci si aspetta che l'output di un programma sia l'input di un altro programma, per cui: non sporcare l'output con dati estranei!
  • Scrivi programmi che gestiscano flussi di testo perché questa è un'interfaccia universale.
  • La modularità si realizza con sottili strati di colla tra le componenti, vanno cioè evitate astrazioni eccessive: il contrario della programmazione ad oggetti.

La shell

L'input da tastiera dell'utente viene interpretato dalla shell, solitamente la shell interattiva è la bash e si presenta così:

$ _

Comandi

Alcuni semplici comandi che useremo in seguito. Le righe che iniziano con $ sono i comandi, quelle senza sono le righe dei risultati:

$ echo Hello world
Hello world

echo scrive su stdout la stringa passata come argomento.

$ cat /etc/debian_version 
9.0

cat scrive su stdout il contenuto dei file passati come argomenti.

$ echo "questa frase contiene 5 parole" | wc
      1       5      31

wc (word count) conta in numero di righe, parole e caratteri dell'input. Sì, il numero di caretteri dovrebbe essere 30 ma echo aggiunge il ritorno a capo; quando si vuole evitare questo effetto collaterale si usa printf invece di echo.

$ nc -l -p 12345

Netcat: nc ha molti utilizzi, in questo caso è usato in modalità server, rimane cioè in ascolto sulla porta tcp 12345 in attesa di connessioni da parte di un client. Come al solito ha all'avvio ha i canali stdin, stdout e stderr collegati al terminale.

Nell'esempio abbiamo usato la versione tradizionale di nc che richiede lo switch -p per la porta su cui ascoltare, la versione openbsd invece lo considererebbe un errore: in questo caso avremmo scritto nc -l 12345.

$ nc localhost 12345

Questo è il client corrispondente: si connette al server in ascolto su localhost porta 12345. Ovviamente se il client si trova su un altra macchina rispetto al server al posto di localhost si usa l'hostname (o indirizzo IP) del server).

I/O

Ogni processo Unix è connesso di default a tre canali (stream):

  1. standard input o stdin
  2. standard output o stdout
  3. standard error o stderr

Di default i tre canali sono connessi al terminale (ad esempio /dev/tty) quindi ricevono l'input dalla tastiera mentre lo stdout è il terminale così come lo è stderr; cioè anche gli errori vengono scritti sul terminale.

Unix considera ogni risorsa di I/O (input/output) come un file, quindi anche questi canali vengono considerati come tali.

Ad ogni file aperto Unix fa corrispondere un descrittore di file numerico: file descriptor.

I file descriptor corrispondenti a stdin, stdout e stderr sono rispettivamente 0, 1 e 2.

Ad altri eventuali file aperti dal programma vengono associati altri file descriptor, i.e. altri numeri.

Redirezione dell'I/O

Gli operatori > e < permettono di redirigere l'I/O.

comando > file Lo stdout di comando viene rediretto su file

$ echo 42 > risposta_alla_domanda_fondamentale

In questo caso 42 invece di essere scritto su stdout viene scritto sul file risposta_alla_domanda_fondamentale (se il file esiste viene sovrascritto e se non esiste viene creato).

comando < file Il contenuto di file viene passato a comando via stdin.

$ md5sum < risposta_alla_domanda_fondamentale
50a2fabfdd276f573ff97ace8b11c5f4  -

In questo caso md5sum riceve via stdin il numero 42.

Notare che è diverso eseguire:

$ md5sum risposta_alla_domanda_fondamentale
50a2fabfdd276f573ff97ace8b11c5f4  risposta_alla_domanda_fondamentale

In questo caso md5sum riceve come argomento il nome del file risposta_alla_domanda_fondamentale, lo apre (il file descriptor da cui legge sarà 3, non 0!) e calcola l'MD5 del contenuto (42).

Semplice chat con netcat

Riprendendo l'esempio precedente:

host-A host-B
$ nc -l 5757 $ nc host-A 5757

Ora l'input di A viene ricevuto dal suo nc e passato a host-B che lo scrive sul proprio terminale. Analogamente quello che viene scritto su host-B viene visualizzato sul terminale di host-A.

Pipe

Importante è il concetto di pipe, implementato dall'operatore |.

La pipe è lo strumento che connette l'output di un programma all'input di un altro.

$ md5sum risposta_alla_domanda_fondamentale | wc 
      1       2      69

In questo caso wc riceve come stdin la stringa 50a2fabfdd276f573ff97ace8b11c5f4 risposta_alla_domanda_fondamentale e ne restituisce il numero di righe, parole e caratteri.

La pipe è unidirezionale: quello che riceve su stdin (dal comando a sinistra) lo inoltra (stdout) al comando a destra che lo riceve come proprio stdin.

Per ottenere le connessioni bidirezionali che vedremo dopo è necessario usare le named pipe. Le named pipe sono file particolari di tipo FIFO: ricordiamoci che per Unix tutto è (dovrebbe) essere un file. Per creare una named pipe si usa il comando mkfifo nomepipe o equivalentemente mknode -p nomepipe.

$ mkfifo f
$ ls -l f
prw-r--r-- 1 debe debe 0 mag 23 10:04 f

La lettera 'p' indica che il file f è una named pipe (se fosse un file regolare sarebbe '-', se fosse una directory 'd', …).

Esempio di utilizzo di backpipe per una connessione bidirezionale

$ mkfifo backpipe
$ nc -l 12345 < backpipe | nc www.google.com 80 > backpipe

Collegando un browser a http://localhost:12345 si ottiene una risposta da google.

Funzione del primo nc

Il primo nc si mette in ascolto sulla porta 12345 dalla quale riceverà input dal client che verrà replicato su stdout, questo verrà passato, via |, allo stdin del secondo nc.

Contestualmente imposta il suo stdin su backpipe (cioè il file descriptor 0 punta a backpipe). L'input ricevuto viene inoltrato al client connesso sulla porta 12345.

Funzione del secondo nc

Riceve in input, attraverso |, l'output del primo nc e provvede ad inoltrarlo a www.google.com:80; quando riceve la risposta da google la scrive su stdout; quest'ultimo, intercettato da >, viene rediretto su backpipe.

Schema

     ---> tcp[nc1]out ---> in[|]out ----> in[nc2]tcp ---------v
browser                                                    www.google.com:80
     <--- tcp[nc1]in <--- out[backpipe]in <--- out[nc2]tcp <---

Quindi la prima pipe, |, permette la connessione browser→google mentre la backpipe quella google→browser.

Chat con netcat attraverso firewall

L'ipotesi è che i due host siano all'interno delle rispettive LAN in cui il firewall blocca connessioni entranti e permette ai client di collegarsi in http e https.

Abbiamo però a disposizione un host esterno, host-C, che accetta connessioni.

host-A host-C host-B
$ mkfifo p
$ cat p | nc -l -p 80 | nc -l -p 443 > p
$ nc host-c 80 $ nc host-c 443

A questo punto host-A e host-B possono chattare passando attraverso host-C.

Reverse shell con netcat

Sull'host remoto (remotehost):

$ sudo -i
# rm -f /tmp/p
# cat /tmp/p | /bin/sh -i 2>&1 | nc -l 9898 > /tmp/p

Sull'host locale:

$ whoami 
debe
$ nc remotehost 9898
# whoami
root

Partiamo da nc. nc riceve input sulla porta 9898 (attenzione che non è lo stdin) e lo manda in output (stdout) e > lo passa in input alla named pipe. Notare che quello che nc riceve, e passa a p, è l'input dell'utente che si è collegato con il suo client alla porta 9898 sull'host remoto.

Il cat legge quello che c'è in p (quindi come dicevamo si tratta dei comandi dell'utente) e butta tutto in stdout; ci pensa poi il | che segue a passare questi comandi alla shell interattiva (la shell si può usare anche in modalità non interattiva per eseguire script) per questo c'è il -i; inoltre c'è anche la redirezione degli errori 2>&1 su stdout che spiego dopo.

La shell, dicevamo, riceve i comandi dell'utente, i risultati li scrive su stdout che tramite il | che segue finiscono in input (questa volta sì stdin) a nc che provvede a passarli al client connesso via tcp.

Se non ci fosse la redirezione stderr su stdout (2>&1) il client non riceverebbe gli errori e nemmeno il prompt dei comandi della shell, ma funzionerebbe lo stesso. Quello che succede nel dettaglio è che quando viene invocato /bin/sh il suo stderr viene fatto puntare a stdin dopodiché stdin viene passato attraverso la pipe | a nc.

stderr ----> stdin -----> nc

SSH port forwarding

SSH normalmente realizza la funzionalità di shell remota attraverso una connessione sicura tra due host. SSH è anche in grado di usare questo canale per trasportare altro tipo di traffico fra e attraverso i due host. Si tratta delle funzionalità di port forwarding e ce ne sono tre tipi:

  1. Local port forwarding
  2. Remote port forwarding
  3. Dynamic port forwarding

Local port forwarding

Il local port forwarding permette di connettere il computer locale ad un server remoto passando per il tunnel SSH.

Può essere necessario quando il servizio che vogliamo utilizzare consente l'accesso solo dal server ssh e non dalla nostra macchina (si usa spesso per connessioni a database che hanno ACL basate su indirizzi IP).

In altri casi è utile per far passare sul canale sicuro un servizio che viaggerebbe in chiaro.

Sintassi

ssh -L localport:host:hostport server

La porta locale localport apre una connessione verso host:hostport passando attraverso il tunnel ssh e uscendo da server.

Per questo esempio usiamo http://ipecho.net/plain che ci dice da quale indirizzo provenie la nostra richiesta http, curl è un client http.

$ curl http://ipecho.net/plain
62.101.86.50

Questo è in nostro indirizzo in condizioni normali.

$ ssh -fNL 5757:ipecho.net:80 62.101.86.55

Ora abbiamo la porta locale 5757 in listen: quando un client si collega la connessione viene inoltrata, attraverso il tunnel e quindi attraverso 62.101.86.55, verso il server http ipecho.net.

Il flag importante è -L. -N fa in modo che ssh attivi la connessione verso 62.101.86.55 senza aprire la shell remota e -f mette il processo in background: insomma rimaniamo sulla macchina locale anche dopo aver fatto partire ssh.

$ curl --header "HOST: ipecho.net" http://localhost:5757/plain
62.101.86.55

Qui curl collegandosi alla porta 5757 sulla macchina locale, raggiunge effettivamente ipecho.net che questa volta ci vede arrivare con indirizzo 62.101.86.55.

Nella richiesta http è necessario aggiungere l'header Host altrimenti il server ipecho.net non ci risponde bene.

Remote port forwarding

In questo caso è il server ssh che può connettersi ad un host remoto attraverso il client ssh.

Utile ad esempio se abbiamo un servizio sul nostro laptop non raggiungibile dall'esterno: ci si connette ad un server pubblico su internet che inoltrerà le connessioni al nostro servizio.

Sintassi

ssh -R port:host:hostport server

Sul server la porta port attende connessioni che verranno inoltrate a host:hostport attraverso il tunnell ssh, quindi uscenti dalla macchina locale.

Ad esempio sul mio laptop ho un server web ma non ho un indirizzo pubblico o il firewall blocca le connessioni entranti. Ho accesso al server pubblico example.org via ssh.

$ ssh -R 8080:localhost:80 example.org

Le connessioni a example.org sono inoltrate al mio laptop, localhost in questo caso è relativo alla macchina locale; posso così pubblicare il mio sito come http://example.org:8080.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment