UNIVERSITÀ DEGLI STUDI DI CATANIA
FACOLTÀ DI SCIENZE MATEMATICHE FISICHE NATURALI
CORSO DI LAUREA IN INFORMATICA
ALESSIA CIRAUDO
PROBLEMATICHE DI
SICUREZZA IN
AMBIENTE LINUX
PROGETTO BARI – CATANIA
Buone Prassi Integrative tra Università e Imprese
Anno Accademico 2002-2003
Spett. le C.O.F. CENTRO ORIENTAMENTO E FORMAZIONE
DELL’UNIVERSITA’ DEGLI STUDI DI CATANIA
VIA A. DI S. GIULIANO N. 262
95131 – CATANIA
La sottoscritta Alessia Ciraudo, iscritta al quinto anno del Corso di Laurea di
Informatica per l'anno accademico 2003-2004, risultata idonea al corso formativo del
“Progetto Bari-Catania: buone prassi integrative tra imprese e università, con la presente fa
presente di aver partecipato allo stage aziendale presso la Globalsystem S.r.l. di Siracusa
per l'intero periodo previsto dal progetto formativo.
Lo scopo principale dello stage aziendale è stato quello di affrontare le
problematiche di sicurezza legate al protocollo TCP IP e alle strutture legate alla gestione
di NETFILTER in memoria e acquisire una migliore conoscenza del sistema operativo
Linux e del linguaggio C, al fine di realizzare uno script in C che funga da base per
l’introduzione di regole e policy d’accesso nelle strutture in memoria addette allo scopo,
usufruibili da un firewall modularizzato di futura realizzazione.
Per poter realizzare ciò, mi sono state date inizialmente le basi della
comunicazione in Internet e della programmazione in C, facendo riferimento a documenti
forniti dall'azienda stessa. Al fine di acquisire meglio i concetti, ogni studio è stato seguito
da applicazioni e relative relazioni.
In particolare, abbiamo iniziato con uno studio dettagliato dei protocolli di rete
(TCP, UDP, ICMP, IP) al fine di ottenere maggiore visibilità sulle problematiche di
sicurezza e firewalling in ambiente Linux. Tale studio è stato accompagnato da sessioni
testuali di tcpdump dettagliatamente descritte nelle Relazioni 1, 1.1, 1.2.
Una volta terminata tale panoramica sui protocolli, siamo passati allo studio delle
funzioni basilari C ANSI STANDARD, attraverso il libro dato in dotazione per l’intera
durata dello stage dalla Globalsystem, “Programmare In C” di Peter Aitken, Bradley L.
Jones, ai fini della corretta stesura del codice scopo finale dello stage.
Avendo, però, già delle basi sulla programmazione in C, ci siamo interessati
particolarmente alle principali chiamate di sistema e funzioni di libreria che sarebbero
servite nel seguito del periodo di stage.
Abbiamo, quindi, effettuato un'analisi della funzione system() e della funzione
gets(), interessandoci in particolare alle relative problematiche di sicurezza ai fini della
corretta stesura del codice scopo finale dello stage. Tale studio è stato accompagnato da un
piccolo codice dimostrativo dettagliatamente descritto nella Relazione 2.
Siamo, quindi, passati allo studio della funzione getopt() e delle chiamate di
sistema execl() ed execv() ai fini della corretta stesura del codice scopo finale dello stage.
Tali studi sono stati accompagnati da diversi codici dimostrativi dettagliatamente descritti,
rispettivamente, nella Relazione 3 e nella Relazione 4.
Infine, abbiamo effettuato uno studio dettagliato del tool di sistema iptables
(Netfilter) ai fini di una completa conoscenza sul sistema di firewalling standard adottato in
ambiente Linux. Tale studio è stato accompagnato da diverse sessioni dimostrative
dettagliatamente descritte nella Relazione 5. E' stata, inoltre, stilata una documentazione,
Relazione 6, per il corretto uso del tool di filtraggio d'amministrazione per il filtraggio dei
pacchetti IPv4.
A questo punto, avendo acquisito le nozioni di base, siamo potuti passare alla
realizzazione dello script in C scopo finale dello stage. Il codice non commentato può
essere richiesto alla società Globalsystem srl. La documentazione tecnica prodotta è
reperibile nella Relazione 7, mentre la dimostrazione della funzionalità del codice è
dettagliatamente descritta nella Relazione 8.
Relazione 1
STUDIO DEI PROTOCOLLI IP E TCP
Il TCP è uno dei due protocolli di trasporto nella suite dei protocolli TCP/IP ed è il
più sicuro per la consegna degli stessi, nel senso che saremo sempre certi che questi
arriveranno a destinazione.
Con il TCP, il client prima di cominciare a spedire direttamente i dati al server,
manda preventivamente dei pacchetti per avvertirlo delle sue intenzioni e per capire se
dall'altra parte lui è pronto a riceverli. Appurato ciò il client comincerà a spedire la parte
più importante della transazione al server: vale a dire i dati veri e propri.
Procediamo con un esempio.
Determiniamo
l'indirizzo
IP
di mail.libero.it
digitando
da
linea di
comando ping mail.libero.it, che ci restituirà il relativo indirizzo 193.70.192.50.
Facciamo partire una sessione tcpdump ristretta al traffico dell'indirizzo IP di
mail.libero.it attraverso la porta 25 (SMTP, determinata analizzando il file /etc/services),
tramite il comando tcpdump -n -vvv -S host 193.70.192.50. Connettiamoci con
mail.libero.it tramite telnet e digitiamo quit, determinando la chiusura della connessione.
Il tcpdump ci restituirà il seguente output (per una maggiore comprensione sono stati
numerati i pacchetti ricevuti):
[root@FlashComa alessia]# tcpdump -n -vvv -S host 193.70.192.50
tcpdump: listening on eth0
1)10:36:17.956202 192.168.0.67.32770 > 193.70.192.50.25: S
2108254613:2108254613(0) win 5840 <mss 1460,sackOK,timestamp
417992[|tcp]> (DF) [tos 0x10] (ttl 64, id 2397, len 60)
2)10:36:18.057410 193.70.192.50.25 > 192.168.0.67.32770: S
499442419:499442419(0) ack 2108254614 win 24616 <nop,nop,
timestamp 1455095388 417992,nop,[|tcp]> (DF) (ttl 51, id 20031,
len 64)
3)10:36:18.057550 192.168.0.67.32770 > 193.70.192.50.25: .
[tcp sum ok] 2108254614:2108254614(0) ack 499442420 win 5840
<nop, nop, timestamp 418002 1455095388>
64, id 2398, len 52)
(DF)
[tos 0x10] (ttl
4)10:36:19.145131 193.70.192.50.25 > 192.168.0.67.32770: P
499442420:499442471(51) ack 2108254614 win 24616 <nop,nop,
timestamp 1455095496 418002> (DF) (ttl 51, id 20032, len 103)
5)10:36:19.145205 192.168.0.67.32770 > 193.70.192.50.25: .
[tcp sum ok] 2108254614:2108254614(0) ack 499442471 win 5840
<nop, nop, timestamp 418110 1455095496> (DF) [tos 0x10] (ttl
64, id 2399, len 52)
6)10:36:31.119893 192.168.0.67.32770 > 193.70.192.50.25: P
2108254614:2108254620(6) ack 499442471 win 5840 <nop,nop,
timestamp 419308 1455095496> (DF) [tos 0x10] (ttl 64, id 2400,
len 58)
7)10:36:31.220623 193.70.192.50.25 > 192.168.0.67.32770: .
[tcp sum ok] 499442471:499442471(0) ack 2108254620 win 24616
<nop, nop, timestamp 1455096704 419308> (DF) (ttl 51, id 20033,
len 52)
8)10:36:31.222640 193.70.192.50.25 > 192.168.0.67.32770: P
499442471:499442497(26) ack 2108254620 win 24616 <nop,nop,
timestamp 1455096704 419308> (DF) (ttl 51, id 20034, len 78)
9)10:36:31.222703 192.168.0.67.32770 > 193.70.192.50.25: .
[tcp sum ok] 2108254620:2108254620(0) ack 499442497 win 5840
<nop,nop,timestamp 419318 1455096704> (DF) [tos 0x10] (ttl 64,
id 2401, len 52)
10)10:36:31.226310 193.70.192.50.25 > 192.168.0.67.32770: F
[tcp sum ok] 499442497:499442497(0) ack 2108254620 win 24616
<nop, nop, timestamp 1455096704 419308> (DF) (ttl 51, id 20035,
len 52)
11)10:36:31.229894 192.168.0.67.32770 > 193.70.192.50.25: F
[tcp sum ok] 2108254620:2108254620(0) ack 499442498 win 5840
<nop,nop,timestamp 419319 1455096704> (DF) [tos 0x10] (ttl 64,
id 2402, len 52)
12)10:36:31.526178 192.168.0.67.32770 > 193.70.192.50.25: F
[tcp sum ok] 2108254620:2108254620(0) ack 499442498 win 5840
<nop,nop,timestamp 419349 1455096704> (DF) [tos 0x10] (ttl 64,
id 2403, len 52)
13)10:36:32.126153 192.168.0.67.32770 > 193.70.192.50.25: F
[tcp sum ok] 2108254620:2108254620(0) ack 499442498 win 5840
<nop, nop, timestamp 419409 1455096704> (DF) [tos 0x10] (ttl 64,
id 2404, len 52)
14)10:36:32.231020 193.70.192.50.25 > 192.168.0.67.32770: .
[tcp sum ok] 499442498:499442498(0) ack 2108254621 win 24616
<nop,nop,timestamp 1455096805 419409>(DF)(ttl 51,id 20036,len 52)
15)10:36:32.434158 193.70.192.50.25 > 192.168.0.67.32770: R
[tcp sum ok] 499442498:499442498(0) win 5840 <nop, nop, timestamp
419319 1455096704> [tos 0x10] (ttl 38, id 2402, len 52)
16)10:36:32.464295 193.70.192.50.25 > 192.168.0.67.32770: R
[tcp sum ok] 499442498:499442498(0) win 5840 <nop, nop, timestamp
419349 1455096704> [tos 0x10] (ttl 38, id 2403, len 52)
16 packets received by filter
0 packets dropped by kernel
Da una prima analisi vediamo che, così come era stato specificato, il flusso dei pacchetti
riguarda solo quelli scambiati tra il client con indirizzo IP 192.168.0.67, il quale usa la
porta 32770, e il server con indirizzo IP 193.70.192.50, che usa la porta 25.
I primi tre pacchetti stabiliscono la connessione fra i due host.
Nel 1° il client invia al server un pacchetto in cui attiva il flag SYN dell'header TCP e
imposta il valore ISN (initial sequence number) a 2108254613. In tal modo il client
comunica la volontà di voler stabilire la connessione; il suo stato passerà da CLOSED a
SYN_SENT.
A questo punto il server invia il proprio ISN, impostando il valore a 499442419, attivando
anch'egli il flag SYN, e nello stesso pacchetto attiva anche il flag ACK, impostando il
numero di acknowledgment a 2108254614, ovvero l'ISN mandato dal client incrementato
di uno. Questa pratica sta ad indicare che il server ha ricevuto correttamente l'ISN del
client e che si può andare avanti nella transazione TCP. Il suo stato, a tal punto passerà da
LISTEN a SYN_RCVD.
Il client, dal canto suo, fa lo stesso, riconoscendo l'ISN mandato dal server con un
pacchetto contenente il flag ACK attivato e il numero di acknowledgment pari a
499442420. Il sequence number è ancora pari a 2108254614 in quanto non sono stati
inviati dati. Il suo stato passa così a ESTABLISHED.
Nel momento in cui il server riceverà, correttamente, tale pacchetto, passerà anch'esso
nello stato ESTABLISHED. Ora la connessione è stabilita e si può procedere con l'invio dei
dati.
Analizzando ancora questi tre pacchetti notiamo che il client imposta nell'header
TCP, tra le opzioni, la massima dimensione del segmento (mss) pari a 1460 byte e il
campo WINDOW a 5840; nell'header IP il tempo massimo di permanenza del pacchetto in
rete (ttl) a 64s e attiva il flag DF (Don't fragment) per impedire la frammentazione del
pacchetto. Il server imposta nell'header TCP il campo WINDOW a 24616 e attiva
anch'esso il flag DF. Nototiamo, inoltre, che il tempo massimo di permanenza del
pacchetto in rete (ttl) risulta pari a 51s, tale valore è quello che risulta in seguito alla
decrementazione del valore impostato inizialmente dal server. Infatti, ogni modulo che
processa un datagramma deve decrementare il ttl di almeno 1, anche se l'elaborazione
viene effettuata in meno di un secondo, allora il ttl può essere pensato come il "limite
superiore" del tempo di vita di un datagramma. Quando tale campo assumerà valore zero,
il datagramma sarà cancellato.
Ogni pacchetto ha anche un numero identificativo, evidenziato dal campo ID
dell'header IP. Inoltre, è possibile determinare la lunghezza totale di ciascun pacchetto
tramite il campo Total Length dell'header IP, corrispondente nell'output del tcpdump al
valore len, ad esempio quella del primo pacchetto è pari a 60 byte, quella del secondo 64
byte, del terzo 52 byte e così via.
Così come è stato visto nel dettaglio nei primi tre pacchetti, sia il client che il server
impostano in ogni pacchetto il valore dell'acknowledgement number (nell'header TCP) pari
al sequence number dell'ultimo pacchetto ricevuto, il campo WINDOW dell'header TCP, il
campo ttl (Time to Live) dell'header IP e, inoltre viene attivato in ogni pacchetto (ad
eccezione degli ultimi due) il flag DF dell'header IP, per evitare la frammentazione del
pacchetto stesso.
Con il 4° pacchetto il server invia 51 byte di dati al client, attivando perciò il flag
PUSH nell'header TCP. Il valore del sequence number viene incrementato in relazione alla
quantità di dati inviati e risulta, quindi, pari a 499442471. Il valore dell'acknowledgment
number è ancora pari a 2108254614.
Il 5° pacchetto viene inviato dal client al server per comunicare l'avvenuta ricezione
del pacchetto precedente. Il client non invia dati, infatti il sequence number assume ancora
il valore 2108254614, ma attiva solamente il flag ACK ponendo il valore del campo
acknowledgment number pari a 499442471 (sequence number del pacchetto precedente).
Il 6° pacchetto corrisponde alla digitazione da parte del client di "quit\r\n". Il client,
infatti, invia al server un pacchetto in cui attiva il flag PUSH per indicare l'invio dei 6 byte
di dati. Il valore del sequence number è ora pari a 2108254620.
Il 7° pacchetto viene inviato dal server al client per comunicare l'avvenuta ricezione
del pacchetto precedente. Il server non invia dati, infatti il sequence number assume ancora
il valore 499442471, ma viene attivato solamente il flag ACK ponendo il valore del campo
acknowledgment number pari a 2108254620.
Con l'8° pacchetto il server invia 26 byte di dati, viene quindi attivato il flag PUSH
e il sequence number assume valore 499442497.
Con il 9° pacchetto il client comunica l'avvenuta ricezione del pacchetto
precedente. Non inviando dati il sequence number resta invariato.
Dal 10° pacchetto inizia la chiusura della connessione.
Il server invia un pacchetto (10°) al client in cui attiva il flag FIN, il sequence number ha
ancora valore 499442497, in quanto non sta inviando dati. Il suo stato passa da
ESTABLISHED a FIN_WAIT_1.
In tal caso possiamo notare una chiusura irregolare della connessione. Di norma, infatti, il
client avrebbe dovuto mandare al server un pacchetto in cui riconosceva il FIN ricevuto e
un altro pacchetto in cui inviava il proprio FIN, il quale sarebbe stato riconosciuto dal
server tramite un nuovo pacchetto in cui veniva attivato solo il flag ACK. In tale
situazione, invece, il client invia al server tre pacchetti (11°, 12° e 13°), in ciascuno dei
quali riconosce il FIN del server e attiva il proprio; il server dopo il terzo invia un
pacchetto (14°) per riconoscere il FIN del client e quindi invia due pacchetti (15° e 16°) in
cui attiva il flag RST (reset) per chiudere immediatamente la connessione.
Riconnettiamoci a mail.libero.it tramite telnet e ricontrolliamo i pacchetti, relativi in
particolare alla chiusura della connessione. L'output ottenuto dal tcpdump sarà:
[root@FlashComa alessia]# tcpdump -n -vvv -S host 193.70.192.50
tcpdump: listening on eth0
1)10:18:23.362409 192.168.0.67.32771 > 193.70.192.50.25: S
1999024212:1999024212(0) win 5840 <mss 1460, sackOK, timestamp
8950532[|tcp]> (DF) [tos 0x10] (ttl 64, id 9040, len 60)
2)10:18:24.388756 193.70.192.50.25 > 192.168.0.67.32771: S
4141009794:4141009794(0) ack 1999024213 win 24616 <nop, nop,
timestamp 2280221171 8950532,nop,[|tcp]> (DF) (ttl 51,id 9087,
len 64)
3)10:18:24.388894 192.168.0.67.32771 > 193.70.192.50.25: .
[tcp sum ok] 1999024213:1999024213(0) ack 4141009795 win 5840
<nop,nop,timestamp 8950635 2280221171> (DF)[tos 0x10] (ttl 64,
id 9041, len 52)
4)10:18:26.861102 193.70.192.50.25 > 192.168.0.67.32771: P
4141009795:4141009846(51) ack 1999024213 win 24616 <nop, nop,
timestamp 2280221418 8950635> (DF) (ttl 51, id 9088, len 103)
5)10:18:26.861205 192.168.0.67.32771 > 193.70.192.50.25: .
[tcp sum ok] 1999024213:1999024213(0) ack 4141009846 win 5840
<nop,nop,timestamp 8950882 2280221418> (DF) [tos 0x10](ttl 64,
id 9042, len 52)
6)10:18:30.587791 192.168.0.67.32771 > 193.70.192.50.25: P
1999024213:1999024219(6) ack 4141009846 win 5840 <nop, nop,
timestamp 8951255 2280221418> (DF) [tos 0x10] (ttl 64,id 9043,
len 58)
7)10:18:31.843956 193.70.192.50.25 > 192.168.0.67.32771: .
[tcp sum ok] 4141009846:4141009846(0) ack 1999024219 win 24616
<nop, nop, timestamp 2280221916 8951255> (DF) (ttl 51,id 9089,
len 52)
8)10:18:31.844989 193.70.192.50.25 > 192.168.0.67.32771: P
4141009846:4141009872(26) ack 1999024219 win 24616 <nop, nop,
timestamp 2280221916 8951255> (DF) (ttl 51, id 9090, len 78)
9)10:18:31.845060 192.168.0.67.32771 > 193.70.192.50.25: .
[tcp sum ok] 1999024219:1999024219(0) ack 4141009872 win 5840
<nop,nop,timestamp 8951380 2280221916> (DF) [tos 0x10](ttl 64,
id 9044, len 52)
10)10:18:31.846512 193.70.192.50.25 > 192.168.0.67.32771: F
[tcp sum ok] 4141009872:4141009872(0) ack 1999024219 win 24616
<nop,nop,timestamp 2280221916 8951255> (DF) (ttl 51, id 9091,
len 52)
11)10:18:31.857369 192.168.0.67.32771 > 193.70.192.50.25: F
[tcp sum ok] 1999024219:1999024219(0) ack 4141009873 win 5840
<nop,nop, timestamp 8951382 2280221916> (DF)[tos 0x10](ttl 64,
id 9045, len 52)
12)10:18:33.256800 193.70.192.50.25 > 192.168.0.67.32771: .
[tcp sum ok] 4141009873:4141009873(0) ack 1999024220 win 24616
<nop,nop, timestamp 2280222058 8951382> (DF) (ttl 51, id 9092,
len 52)
12 packets received by filter
0 packets dropped by kernel
Notiamo che il server (indirizzo IP 193.70.192.50) usa sempre la porta 25, mentre il
client (indirizzo IP 192.168.0.67) usa ora la porta 32771.
La chiusura della connessione inizia con il 10° pacchetto, inviato dal server al client, in
cui viene attivato il flag FIN. Il client risponde al server con un pacchetto in cui riconosce
il FIN del server e invia il suo. Quest'ultimo viene riconosciuto dal server nell'ultimo
pacchetto. A questo punto sia il client che il server si trovano nello stato CLOSED e la
connessione è chiusa correttamente.
Relazione 1.1
STUDIO DEL PROTOCOLLO UDP
Oltre al TCP, un altro protocollo di trasporto è l'UDP. Questo è detto
connectionless protocol, in quanto non assicura la consegna dei dati .
Quando da un lato e dall'altro stanno un client e un server UDP, il primo comincerà a
trasmettere dati al secondo senza avvertirlo, senza controllare se è pronto a riceverli.
Iniziamo una sessione tcpdump ristretta al traffico dei pacchetti sulla porta 53 (porta
del DNS). Usiamo il comando ping su qualche hostname e analizziamo l'output del
tcpdump.
[root@FlashComa alessia]# /usr/sbin/tcpdump -n -vvv port 53
tcpdump: listening on eth0
1)11:27:39.254684 192.168.0.67.32768 > 212.210.247.130.53:
32370+[|domain] (DF) (ttl 64, id 60009, len 60)
2)11:27:39.256190 212.210.247.130.53 > 192.168.0.67.32768:
32370 q:[|domain] (ttl 63, id 30839, len 131)
3)11:28:30.797762 192.168.0.67.32768 > 212.210.247.130.53:
45451+[|domain] (DF) (ttl 64, id 65164, len 59)
4)11:28:31.571589 212.210.247.130.53 > 192.168.0.67.32768:
45451 NXDomain q:[|domain] (ttl 63, id 31219, len 134)
5)11:28:31.572199 192.168.0.67.32768 > 212.210.247.130.53:
45452+[|domain] (DF) (ttl 64, id 65241, len 59)
6)11:28:31.572945 212.210.247.130.53 > 192.168.0.67.32768:
45452 NXDomain q:[|domain] (ttl 63, id 31220, len 134)
7)11:28:55.740621 192.168.0.67.32768 > 212.210.247.130.53:
[udp sum ok] 37876+ A? unict.it. (26) (DF) (ttl 64, id 2122,
len 54)
8)11:28:56.269639 212.210.247.130.53 > 192.168.0.67.32768:
37876 q: A? unict.it. 1/4/2 [|domain] (ttl 63, id 31299,
len 185)
9)11:29:31.622749 192.168.0.67.32768 > 212.210.247.130.53:
55696+[|domain] (DF) (ttl 64, id 5710, len 58)
10)11:29:33.746865 212.210.247.130.53 > 192.168.0.67.32768:
55696 q:[|domain] (ttl 63, id 31487, len 308)
11)11:29:35.504798 192.168.0.67.32768 > 212.210.247.130.53:
55697+[|domain] (DF) (ttl 64, id 6098, len 72)
12)11:29:37.955943 212.210.247.130.53 > 192.168.0.67.32768:
55697 q:[|domain] (ttl 63, id 31516, len 153)
13)11:29:40.240181 192.168.0.67.32768 > 212.210.247.130.53:
55698+[|domain] (DF) (ttl 64, id 6572, len 72)
14)11:29:40.241651 212.210.247.130.53 > 192.168.0.67.32768:
55698 q:[|domain] (ttl 63, id 31520, len 153)
15)11:29:41.122727 192.168.0.67.32768 > 212.210.247.130.53:
55699+[|domain] (DF) (ttl 64, id 6660, len 72)
16)11:29:41.124364 212.210.247.130.53 > 192.168.0.67.32768:
55699 q:[|domain] (ttl 63, id 31534, len 153)
17)11:29:42.004251 192.168.0.67.32768 > 212.210.247.130.53:
55700+[|domain] (DF) (ttl 64, id 6748, len 72)
18)11:29:42.005551 212.210.247.130.53 > 192.168.0.67.32768:
55700 q:[|domain] (ttl 63, id 31539, len 153)
18 packets received by filter
0 packets dropped by kernel
Analizziamo in particolare quattro pacchetti.
Nel 5° il client (indirizzo IP 192.168.0.67) richiede, attraverso la porta 32768, un
servizio al server DNS (indirizzo IP 212.210.247.130). Quest'ultimo risponde, usando la
porta 53, informando l'inesistenza del dominio specificato (NXDomain).
Vediamo, ora, il 7° e l'8° pacchetto (gli altri sono analoghi).
Il client effettua una nuova richiesta al server DNS per identificare l'indirizzo IP
dell'hostname unict.it. Questa è una richiesta ricorsiva (come si nota dalla presenza del +),
ovvero se il server non riesce a dare una risposta deve girare la richiesta a qualche altro
server. L'8° pacchetto costituisce proprio la risposta del server, il quale questa volta ha
trovato l'indirizzo IP richiesto.
Come nello studio dei pacchetti precedenti, si possono dedurre delle informazioni
dall'header IP, quali il tempo massimo di permanenza del pacchetto in rete (ttl), 64s nelle
richieste; il numero identificativo del pacchetto, rispettivamente 65241, 31220, 2122 e
31299; la lunghezza totale, 59 byte il 5°, 134 byte il 6°, 54 byte il 7° e 185 byte l'8°;
l'attivazione del flag DF nei pacchetti inviati dal client.
Al fine di evitare di troncare la visualizzazione delle informazioni, aggiungiamo
l'opzione -s 500 al tcpdump ed effettuiamo il ping all'hostname alitalia.it. Otterremo:
[root@FlashComa alessia]# tcpdump -n -vvv -s 500 port 53
tcpdump: listening on eth0
1) 12:02:18.337656 192.168.0.67.32768 > 212.210.247.130.53:
[udp sum ok] 41530+ A? www.alitalia.it. (33) (DF) (ttl 64,
id 5774, len 61)
2) 12:02:18.832327 212.210.247.130.53 > 192.168.0.67.32768:
[udp sum ok] 41530 q: A? www.alitalia.it. 1/4/0 www.alitalia.it. A
80.72.160.79 ns: alitalia.it. NS dns2.alitalia.it., alitalia.it. NS
dns.alitalia.it, alitalia.it. NS dns.interbusiness.it.,alitalia.it.
NS dns2.nic.it. (141) (ttl 63, id 40897, len 169)
3) 12:02:19.947529 192.168.0.67.32768 > 212.210.247.130.53:
[udp sum ok] 41531+ PTR? 79.160.72.80.in-addr.arpa. (43) (DF)
(ttl 64, id 5935, len 71)
4) 12:02:20.096321 212.210.247.130.53 > 192.168.0.67.32768:
[udp sum ok] 41531 NXDomain q: PTR? 79.160.72.80.in-addr.arpa.
0/1/0 ns: 80.in-addr.arpa. SOA ns.ripe.net. ops-80.ripe.net.
2003073002 43200 7200 1209600 7200 (97)(ttl 63,id 40904,len 125)
4 packets received by filter
0 packets dropped by kernel
Con il 1° pacchetto il client chiede, ricorsivamente, al server l'indirizzo IP di
www.alitalia.it, inviandogli 33 byte di dati. Il server DNS risponde alla richiesta del client
specificando i server DNS a cui si è rivolto per determinare l'indirizzo IP richiesto. A
questo punto il client chiede al server, come conferma, l'hostname dell'indirizzo IP
specificato, ma a tale richiesta il server risponde indicando l'inesistenza di tale hostname.
Sottolineamo il fatto che questo non è sinonimo di errore, in quanto spesso accade che
sebbene il server abbia l'associazione hostname -> indirizzo IP, esso sia sprovvisto del
viceversa indirizzo IP -> hostname.
L'individuazioni delle informazioni estratte dall'header IP è analogo ai casi precedenti.
Relazione 1.2
STUDIO DEL PROTOCOLLO ICMP
L'Internet Control Message Protocol (ICMP) viene usato per due tipi di operazioni:
− quando un router o un host di destinazione deve informare l'host mittente riguardo un
errore nell'elaborazione del datagramma;
− per testare la rete con messaggi di "richiesta" e "risposta", in modo da determinare
caratteristiche generali della rete stessa.
Ad ogni messaggio ICMP viene assegnato un codice numerico, detto message type, che
specifica il tipo di messaggio e un altro codice, detto code, il quale può essere visto come
un sotto-tipo, ovvero ha il compito di specificare meglio alcuni tipi di messaggi; per tale
motivo il suo valore dipende strettamente dal tipo di messaggio.
Come è già stato indicato, l'ICMP svolge sostanzialmente due tipi di operazioni, quindi
possiamo dire che esistono due tipi di messaggi:
− ICMP Error Messages;
− ICMP Query Messages.
I messaggi ICMP di errore vengono usati per riferire all'host mittente un problema
pervenuto durante la consegna del pacchetto. Questi includeranno l'header IP (da 20 a 60
byte) e almeno i primi 8 byte dei dati del datagramma che ha provocato l'errore. Esistono
diversi tipi di messaggi di errore, fra cui l'impossibilità di consegnare il pacchetto all'host
di destinazione in quanto il router necessita di effettuare una frammentazione, ma il flag
DF è stato settato nell'header IP, in tal caso il router invierà all'host mittente un messaggio
ICMP di errore di type 3 e code 4.
I messaggi ICMP di query, invece, servono per interrogare un host su una certa
richiesta e attendono l'arrivo di una risposta. Se questa arriva è chiaro quindi che l'host di
destinazione è attivo e non è down.
Il ping usa i messaggi di query echo request e si aspetta l'arrivo di un echo reply da parte
del destinatario. Se questo sopraggiunge vuol dire che l'host è attivo, altrimenti l'indirizzo
IP preso in considerazione non sta in rete o è protetto da firewall.
Concentriamoci in particolar modo ai messaggi ICMP di query. Usiamo ancora il
comando tcpdump, questa volta ristretto al traffico dei pacchetti ICMP, e il comando
ping. L'output del tcpdump che otterremo sarà:
[root@FlashComa alessia]# tcpdump icmp -n -vvv -s 200
tcpdump: listening on eth0
1) 10:44:59.657346 192.168.0.67 > 209.73.180.8: icmp: echo
request (DF) (ttl 64, id 0, len 84)
2) 10:45:00.495493 209.73.180.8 > 192.168.0.67: icmp: echo
reply (DF) (ttl 236, id 42244, len 84)
3) 10:45:01.943193 192.168.0.67 > 209.73.180.8: icmp: echo
request (DF) (ttl 64, id 0, len 84)
4) 10:45:02.433920 209.73.180.8 > 192.168.0.67: icmp: echo
reply (DF) (ttl 236, id 42477, len 84)
5) 10:45:27.128641 192.168.0.67 > 151.97.1.6: icmp: echo
request (DF) (ttl 64, id 0, len 84)
6) 10:45:28.146192 192.168.0.67 > 151.97.1.6: icmp: echo
request (DF) (ttl 64, id 0, len 84)
6 packets received by filter
0 packets dropped by kernel
Inizialmente il client (indirizzo IP 192.168.0.67) invia un messaggio ICMP di query
"echo request" all'host con indirizzo IP 209.73.180.8 (altavista.it), che risponde con un
messaggio ICMP di query "echo reply". In seguito il client invia un altro echo request
all'host 209.73.180.8. a cui quest'ultimo risponde con un nuovo echo reply. Infine il client
invia due echo request all'host 151.97.1.6 (unict.it) senza avere alcuna risposta. Come gia
detto, questo può esser dovuto a due motivi:
- l'host non è collegato alla rete;
- esiste un firewall che impedisce l'invio di una risposta o blocca in ricezione le query.
Relazione 2
FUNZIONE SYSTEM()
La funzione system() consente di eseguire comandi durante l'esecuzione del
programma. Essa prende in input il comando da eseguire e restituisce lo stato del comando,
nel caso in cui quest'ultimo sia un comando valido, "-1" altrimenti.
Scriviamo un semplice programma che ci elenchi tutti i file presenti nella directory
corrente (inclusi i file nascosti) con le relative informazioni (permessi, dimesione, ecc..).
#include <stdlib.h>
int main()
{
system("ls -al");
}
L'output che otterremo sarà il seguente:
[alessia@FlashComa alessia]$ ./system1
totale 208
drwx------ 7 alessia alessia 4096 ago
drwxr-xr-x 6 root
root
4096 lug
-rw------- 1 alessia alessia
725 ago
-rw-r--r-- 1 alessia alessia
24 lug
-rw-r--r-- 1 alessia alessia
191 lug
-rw-r--r-- 1 alessia alessia
124 lug
drwx------ 3 alessia alessia 4096 lug
-rw-rw-r-- 1 alessia alessia
0 lug
-rw-rw-r-- 1 alessia alessia 38618 lug
-rw-r--r-- 1 alessia alessia
120 lug
-rw-rw-r-- 1 alessia alessia 1112 lug
-rw------- 1 alessia alessia
0 lug
drwxr-xr-x 4 alessia alessia 4096 lug
drwxr-xr-x 3 alessia alessia 4096 lug
-rw------- 1 alessia alessia
31 lug
-rwxrwxr-x 1 alessia alessia 11771 ago
-rw-rw-r-- 1 alessia alessia
581 ago
-rwxrwxr-x 1 alessia alessia 11938 ago
-rw-rw-r-- 1 alessia alessia
725 ago
-rwxrwxr-x 1 alessia alessia 11940 ago
-rw-rw-r-- 1 alessia alessia
624 ago
drwxrwxr-x 2 alessia alessia 4096 lug
drwx------ 2 alessia alessia 4096 lug
-rwxrwxr-x 1 alessia alessia 11356 ago
-rw-rw-r-- 1 alessia alessia
56 ago
-rw------- 1 alessia alessia
864 lug
-rw------- 1 alessia alessia
103 lug
7
28
6
28
28
28
31
28
28
28
29
31
28
28
29
6
6
6
6
6
6
28
29
7
7
31
29
11:15
12:26
12:30
12:26
12:26
12:26
17:11
12:33
12:34
12:26
09:28
17:11
12:33
12:34
09:28
11:34
11:34
11:35
11:35
11:35
11:35
12:34
09:29
11:15
11:04
12:40
09:28
.
..
.bash_history
.bash_logout
.bash_profile
.bashrc
Desktop
.first_start_kde
.fonts.cache-1
.gtkrc
.gtkrc-kde
.ICEauthority
.kde
.mcop
.mcoprc
opt1
opt1.c
opt2
opt2.c
opt3
opt3.c
.qt
.ssh
system1
system1.c
.viminfo
.Xauthority
[alessia@FlashComa alessia]$
Possiamo pensare di estendere il nostro programma, in modo che accetti l'input da tastiera:
#include <stdlib.h>
int main()
{
char cmd[30];
printf ("Inserisci un comando\n");
gets(cmd);
system(cmd);
}
la cui esecuzione ci produrrà
[alessia@FlashComa alessia]$ ./system2
Inserisci un comando
ls
alessia2.txt
alessia.txt
Desktop
opt1.c
opt2.c
opt3.c
system1.c
system2.c
alessia(icmp).txt
alessia(udp).txt
opt1
opt2
opt3
system1
system2
[alessia@FlashComa alessia]$
Se dovessimo inserire un comando non valido, questo ci verrà segnalato nel seguente
modo:
[alessia@FlashComa alessia]$ ./system2
Inserisci un comando
ciao
sh: line 1: ciao: command not found
[alessia@FlashComa alessia]$
Come si può notare durante la fase di compilazione di system2.c, la chiamata di sistema
gets() è fortemente sconsigliata. Andando a guardare la pagina di manuale relativa a tale
funzione, possiamo leggere fra i bugs, che con l'uso di gets() risulta impossibile
determinare a priori la quantità di dati che l'utente intende inserire. Ciò risulta essere un
enorme problema, poiché finchè non si ha un'interruzione di linea (\n), gets() continuerà a
memorizzare i caratteri nel buffer, senza effettuare nessun controllo su quest'ultimo.
Relazione 3
FUNZIONE GETOPT()
La funzione getopt() effettua una scansione degli argomenti da linea di comando. I suoi
argomenti sono:
- argc, numero degli argomenti;
- argv, array di caratteri costituenti gli argomenti passati alla funzione principale;
- optstring, una stringa contenente i caratteri opzione legittimi, dove un carattere opzione
non è altro che un argomento di argv iniziante con un “-“.
Se in optstring un carattere è seguito da “:“, allora quell'opzione richiede un argomento.
La funzione getopt() restituisce il carattere opzione se la relativa opzione è stata trovata;
":" se c'è un parametro mancante per una delle opzioni; "?" per caratteri opzione
sconosciuti; -1 per la fine della lista delle opzioni.
Iniziamo con questo semplice programmma:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int opt;
while ((opt = getopt(argc, argv, "abcde")) != EOF)
{
switch (opt)
{
case 'a':
printf("Option a\n");
break;
case 'b':
printf("Option b\n");
break;
case 'c':
printf("Option c\n");
break;
case 'd':
printf("Option d\n");
break;
case 'e':
printf("Option e\n");
break;
default:
printf("usage: %s [-a] [-b] [-c] [-d] [-e]\r\n",
argv[0]);
break;
}
}
}
Questo non fa altro che analizzare tutte le opzioni inserite negli argomenti e determinare se
ciascuna opzione è valida, nel caso contrario visualizza il modo in cui usare correttamente
il programma. Mostriamo qualche possibile output:
Primo caso: non viene inserita alcuna opzione
[alessia@FlashComa alessia]$ ./opt1
[alessia@FlashComa alessia]$
Secondo caso: uso di opzioni corrette
[alessia@FlashComa alessia]$ ./opt1 -b -a -c
Option b
Option a
Option c
[alessia@FlashComa alessia]$
Terzo caso: uso di opzioni non valide
[alessia@FlashComa alessia]$ ./opt1 -b -f -c
Option b
./opt1: invalid option -- f
usage: ./opt1 [-a] [-b] [-c] [-d] [-e]
Option c
[alessia@FlashComa alessia]$
Quarto caso: uso di opzioni corrette e aggiunta di argomenti non richiesti
[alessia@FlashComa alessia]$ ./opt1 -b ggg -a -c
Option b
Option a
Option c
[alessia@FlashComa alessia]$
Apportiamo un miglioramento al nostro programma. Introduciamo una funzione errore,
che ci mostri l'uso corretto del programma nel caso in cui venga inserita un'opzione
scorretta o non venga inserita alcuna opzione.
#include <stdio.h>
#include <unistd.h>
void funerr(char *nome);
int main(int argc, char **argv)
{
int opt;
if (argc<2)
{
funerr(argv[0]);
exit();
}
while ((opt = getopt(argc, argv, "abcde")) != EOF)
{
switch (opt)
{
case 'a':
printf("Option a\n");
break;
case 'b':
printf("Option b\n");
break;
case 'c':
printf("Option c\n");
break;
case 'd':
printf("Option d\n");
break;
case 'e':
printf("Option e\n");
break;
default:
funerr(argv[0]);
break;
}
}
}
void funerr(char *nome)
{
printf("usage: %s [-a] [-b] [-c] [-d] [-e]\r\n", nome);
}
I possibili output che otterremo sono i seguenti.
Primo caso: non viene inserita nessuna opzione
[alessia@FlashComa alessia]$ ./opt2
usage: ./opt2 [-a] [-b] [-c] [-d] [-e]
[alessia@FlashComa alessia]$
Secondo caso: uso di opzioni corrette
[alessia@FlashComa alessia]$ ./opt2 -b -a -c
Option b
Option a
Option c
[alessia@FlashComa alessia]$
Terzo caso: uso di opzioni non valide
[alessia@FlashComa alessia]$ ./opt2 -b -f -c
Option b
./opt2: invalid option -- f
usage: ./opt2 [-a] [-b] [-c] [-d] [-e]
Option c
[alessia@FlashComa alessia]$
Quarto caso: uso di argomenti non richiesti
[alessia@FlashComa alessia]$ ./opt2 -b ggg -a -c
Option b
Option a
Option c
[alessia@FlashComa alessia]$
Modifichiamo ulteriormente il programma in modo da richiedere per alcune opzioni un
argomento. La funzione getopt() userà la variabile esterna optarg per puntare a tale
argomento.
#include <stdio.h>
#include <unistd.h>
void funerr(char *nome);
int main(int argc, char **argv)
{
int opt;
if (argc<2)
{
funerr(argv[0]);
exit();
}
while ((opt = getopt(argc, argv, "a:b:cd:e")) != EOF)
{
switch (opt)
{
case 'a':
case 'b':
case 'd':
printf("Option %c\n", opt);
printf("Argument %s\n", optarg);
break;
case 'c':
case 'e':
printf("Option %c\n", opt);
break;
default:
funerr(argv[0]);
break;
}
}
}
void funerr(char *nome)
{
printf("usage: %s [-a] [-b] [-c] [-d] [-e]\r\n", nome);
}
Si avranno i seguenti casi:
Primo caso: non viene inserita alcuna opzione
[alessia@FlashComa alessia]$ ./opt3
usage: ./opt3 [-a] [-b] [-c] [-d] [-e]
[alessia@FlashComa alessia]$
Secondo caso: uso delle opzioni corrette e con i dovuti argomenti
[alessia@FlashComa alessia]$ ./opt3 -a ggg -c -d kkk
Option a
Argument ggg
Option c
Option d
Argument kkk
[alessia@FlashComa alessia]$
Terzo caso: uso di opzioni non valide
[alessia@FlashComa alessia]$ ./opt3 -a ggg -c -d kkk -f
Option a
Argument ggg
Option c
Option d
Argument kkk
./opt3: invalid option -- f
usage: ./opt3 [-a] [-b] [-c] [-d] [-e]
[alessia@FlashComa alessia]$
Quarto caso: omissione di argomenti quando richiesti. In tal caso si possono verificare due
situazioni:
1) l'argomento mancante è l'ultimo nella lista, allora segnala un errore
[alessia@FlashComa alessia]$ ./opt3 -a ggg -c -d
Option a
Argument ggg
Option c
./opt3: option requires an argument -- d
usage: ./opt3 [-a] [-b] [-c] [-d] [-e]
[alessia@FlashComa alessia]$
2) l'argomento mancante è interno alla lista, allora non viene segnalato alcun errore,
ma si considera come argomento la prossima stringa, ignorando il fatto che
potrebbe essere un'altra opzione
[alessia@FlashComa alessia]$ ./opt3 -a -c -d kkk
Option a
Argument -c
Option d
Argument kkk
[alessia@FlashComa alessia]$
Relazione 4
SYSTEM CALL EXECL ED EXECV
L'insieme delle chiamate di sistema exec permettono l'esecuzione di un comando.
In generale, ogni funzione appartenente a tale famiglia prende come primo argomento il
pathname del file da eseguire. Consideriamo in particolare due funzioni: execl ed execv.
Execl prende in input il pathname del file e una lista di uno o più puntatori a stringhe
corrispondenti agli argomenti necessari per eseguire correttamente il programma
specificato.
Execv prende in input ancora il pathname del file, ma, anziché la lista degli argomenti,
un'array di puntatori a stringhe con la stessa funzionalità di quelle precedenti.
Nel caso di errore, verrà restituito il valore -1 e la variabile globale errno sarà settata
per indicare l'errore.
Iniziamo con un semplice esempio.
Scriviamo uno script C per eseguire il comando ls -al /home/alessia
#include <unistd.h>
int main()
{
execl("/bin/ls", "ls", "-al", "/home/alessia", 0);
}
Guardiamo meglio i parametri passati alla system call execl:
- pathname del file da eseguire (/bin/ls);
- nome del file da eseguire (ls), è una convenzione usata da ogni funzione exec;
- parametri del comando da eseguire (-al /home/alessia);
- puntatore nullo (0), indicante la fine dell'elenco.
L'esecuzione del programma ci darà i seguenti risultati:
[alessia@FlashComa alessia]$ ./execl1
totale 256
drwx-----7 alessia alessia
drwxr-xr-x
6 root
root
-rw------1 alessia alessia
-rw-r--r-1 alessia alessia
-rw-r--r-1 alessia alessia
-rw-r--r-1 alessia alessia
drwx-----3 alessia alessia
-rwxrwxr-x
1 alessia alessia
-rw-rw-r-1 alessia alessia
-rw-rw-r-1 alessia alessia
-rw-r--r-1 alessia alessia
-rw-rw-r-1 alessia alessia
-rw------1 alessia alessia
drwxr-xr-x
4 alessia alessia
drwxr-xr-x
3 alessia alessia
-rw------1 alessia alessia
-rw-rw-r-1 alessia alessia
-rw-rw-r-1 alessia alessia
-rwxrwxr-x
1 alessia alessia
-rw-rw-r-1 alessia alessia
drwxrwxr-x
2 alessia alessia
drwx-----2 alessia alessia
-rwxrwxr-x
1 alessia alessia
-rw-rw-r-1 alessia alessia
-rwxrwxr-x
1 alessia alessia
-rw-rw-r-1 alessia alessia
-rwxrwxr-x
1 alessia alessia
-rw-rw-r-1 alessia alessia
-rw------1 alessia alessia
-rw------1 alessia alessia
[alessia@FlashComa alessia]$
4096
4096
3607
24
191
124
4096
11394
88
38618
120
1112
0
4096
4096
31
581
725
11940
624
4096
4096
11356
56
11574
118
11501
170
1096
103
ago
lug
ago
lug
lug
lug
lug
ago
ago
lug
lug
lug
lug
lug
lug
lug
ago
ago
ago
ago
lug
lug
ago
ago
ago
ago
ago
ago
ago
lug
25
28
7
28
28
28
31
25
25
28
28
29
31
28
28
29
6
6
6
6
28
29
7
7
7
7
7
7
25
29
11:19
12:26
13:28
12:26
12:26
12:26
17:11
11:19
11:19
12:34
12:26
09:28
17:11
12:33
12:34
09:28
11:34
11:35
11:35
11:35
12:34
09:29
11:48
11:48
12:26
12:19
13:27
13:27
11:14
09:28
.
..
.bash_history
.bash_logout
.bash_profile
.bashrc
Desktop
execl1
execl1.c
.fonts.cache
.gtkrc
.gtkrc-kde
.ICEauthority
.kde
.mcop
.mcoprc
opt1.c
opt2.c
opt3
opt3.c
.qt
.ssh
system1
system1.c
system2
system2.c
system3
system3.c
.viminfo
.Xauthority
Modifichiamo il programma in modo da dare in input il file da eseguire, usando la
funzione di libreria getopt.
#include <unistd.h>
#include <stdio.h>
void funerr(char *nome);
int main(int argc, char **argv)
{
int opt;
char *path = ""; // pathname del file da eseguire
char *param[10]; // argomenti
int i = 1;
/* controllo sul numero di argomenti passati al main */
if (argc<2)
funerr(argv[0]);
param[0]="";
/* acquisizione degli argomenti validi, corrispondenti ai
parametri da passare a execv
*/
while ((opt = getopt(argc, argv,"p:f:a:")) != EOF)
{
switch(opt)
{
case 'p':
path = optarg;
break;
case 'f':
param[0] = optarg;
break;
case 'a':
param[i] = optarg;
i++;
break;
default:
funerr(argv[0]);
break;
}
}
param[i] = NULL;
/* controllo degli argomenti di execv */
if (path=="" || param[0]=="")
funerr(argv[0]);
execv(path, param);
}
void funerr(char *nome)
{
printf("usage: %s -p pathname -f filename [-a argument]\r\n",
nome);
exit();
}
Prima di vedere l'output, diamo un'occhiata al codice.
Inizialmente viene effettuato un controllo sugli argomenti del main. Se, infatti, non viene
passato alcun parametro, allora viene invocata la funzione funerr, la quale visualizzerà il
corretto uso del programma, e il programma stesso verrà terminato.
Superato tale controllo, vengono memorizzati i parametri validi da passare in seguito alla
system call execv. In particolare, l'opzione -p indica il pathname del file da eseguire,
memorizzato nella variabile path; l'opzione -f indica il nome del file da eseguire,
memorizzato come primo elemento dell'array param; l'opzione -a indica gli (eventuali)
argomenti da passare al file.
A questo punto diamo il valore NULL al primo elemento non assegnato dell'array param,
per il corretto funzionamento della system call execv ed effettuiamo un ultimo controllo.
Dobbiamo, infatti, accertarci che l'utente abbia effettivamente inserito il pathname e il
nome del file da eseguire.
Superato anche questo controllo, possiamo finalmente usare execv per eseguire il file
desiderato.
Vediamo dei possibili output che possiamo ottenere:
Primo caso: non vengono inseriti tutti i parametri per execv
[alessia@FlashComa alessia]$ ./exec2 -p /bin/ls -a /home/alessia
usage: ./exec2 -p pathname -f filename [-a argument]
[alessia@FlashComa alessia]$
[alessia@FlashComa alessia]$ ./exec2 -f ls -a /home/alessia
usage: ./exec2 -p pathname -f filename [-a argument]
[alessia@FlashComa alessia]$
Secondo caso: immissione corretta dei parametri
[alessia@FlashComa alessia]$ ./exec2 -p /bin/ls -f
-a /home/alessia
totale 172
drwx------ 3 alessia alessia 4096 lug 31 17:11
-rwxrwxr-x 1 alessia alessia 12054 ago 25 12:45
-rw-rw-r-- 1 alessia alessia 1038 ago 25 12:45
-rwxrwxr-x 1 alessia alessia 11394 ago 25 11:19
-rw-rw-r-- 1 alessia alessia
88 ago 25 11:19
-rw-rw-r-- 1 alessia alessia
581 ago 6 11:34
-rw-rw-r-- 1 alessia alessia
725 ago 6 11:35
-rw-rw-r-- 1 alessia alessia
624 ago 6 11:35
-rw-rw-r-- 1 alessia alessia
56 ago 7 11:48
-rw-rw-r-- 1 alessia alessia
118 ago 7 12:19
-rw-rw-r-- 1 alessia alessia
170 ago 7 13:27
[alessia@FlashComa alessia]$
ls -a -l
Desktop
exec2
exec2.c
execl1
execl1.c
opt1.c
opt2.c
opt3.c
system1.c
system2.c
system3.c
Relazione 5
IPTABLES
Iptables è un tool di filtraggio di pacchetti IPv4 per Linux. Sostanzialmente esso è
composto da tre tabelle: FILTER, NAT e MANGLE. Ogni tabella è costituita da “catene”
(chains), ovvero liste di regole da confrontare con ogni pacchetto.
FILTER (tabella di default) è costituita da tre catene: INPUT, OUTPUT e
FORWARD; per il controllo dei pacchetti, rispettivamente, in entrata, generati localmente
(e quindi in uscita) e da ridirigere.
NAT, usata per i pacchetti che stabiliscono la connessione (caratterizzati dall'attivazione
del flag SYN nell'header TCP), è costituita dalle catene: PREROUTING, OUTPUT e
POSTROUTING; per il controllo dei pacchetti in entrata prima della decisione di routing,
generati localmente prima del routing e in uscita.
MANGLE, manipola i pacchetti in entrata e uscita e ha due catene principali:
PREROUTING e OUTPUT; per i pacchetti in entrata e uscita, entrambi prima della
decisione di routing.
Una volta che un pacchetto soddisfa una regola, verrà applicato ad esso la regola
corrispondente, la quale può essere il nome di una catena definita dall'utente; DROP, per
scartare il pacchetto; ACCEPT, per accettare il pacchetto; QUEUE, per metterlo in coda;
RETURN, per interrompere il controllo nell'attuale catena e ritornare a quella chiamante.
Concentriamoci, adesso, sulla tabella FILTER. Ricordiamo che essa è considerata come
tabella di default, quindi possiamo omettere l'opzione -t per specificare la tabella.
Procediamo con alcuni esempi di regole.
Consideriamo un server con indirizzo IP 192.168.0.213 e un client con indirizzo IP
192.168.0.200.
Alla fine di ogni esempio “ripuliremo” ogni catena con il comando
[root@Alexia root]# iptables -F
Effettuiamo dei ping dal client verso il server:
C:\>ping 192.168.0.213
Esecuzione di Ping 192.168.0.213 con 32 byte di dati:
Risposta
Risposta
Risposta
Risposta
da
da
da
da
192.168.0.213:
192.168.0.213:
192.168.0.213:
192.168.0.213:
byte=32
byte=32
byte=32
byte=32
durata<10ms
durata<10ms
durata<10ms
durata<10ms
TTL=64
TTL=64
TTL=64
TTL=64
Statistiche Ping per 192.168.0.213:
Pacchetti: Trasmessi = 4, Ricevuti = 4, Persi = 0 (0% persi),
Tempo approssimativo percorsi andata/ritorno in millisecondi:
Minimo = 0ms, Massimo = 0ms, Medio = 0ms
C:\>
blocchiamo ora il traffico relativo al client considerato
[root@Alexia root]# iptables -A INPUT -s 192.168.0.200 -j DROP
Stiamo, in pratica, aggiungendo (A) alla catena INPUT della tabella FILTER la regola
secondo cui tutti i pacchetti provenienti (s) dal client devono essere rifiutati (DROP).
A questo punto se riproviamo ad effettuare dei ping dal client verso il server, notiamo che
non otterremo alcuna risposta:
C:\>ping 192.168.0.213
Esecuzione di Ping 192.168.0.213 con 32 byte di dati:
Richiesta
Richiesta
Richiesta
Richiesta
scaduta.
scaduta.
scaduta.
scaduta.
Statistiche Ping per 192.168.0.213:
Pacchetti: Trasmessi = 4, Ricevuti = 0, Persi = 4 (100% persi),
Tempo approssimativo percorsi andata/ritorno in millisecondi:
Minimo = 0ms, Massimo = 0ms, Medio = 0ms
C:\>
Proviamo, adesso a bloccare tutto il traffico in uscita dal server. Come prima, mostriamo
innanzitutto un'applicazione che permetta al server di inviare dati all'esterno:
[root@Alexia root]# telnet mail.libero.it 25
Trying 193.70.192.50...
Connected to mail.libero.it.
Escape character is '^]'.
220 smtp0.libero.it ESMTP Service (7.0.019) ready
quit
221 smtp0.libero.it QUIT
Connection closed by foreign host.
[root@Alexia root]#
Ora aggiungiamo (A) nella catena OUTPUT la regola
[root@Alexia root]# iptables -A OUTPUT -s 192.168.0.213 -j DROP
secondo cui i pacchetti il cui mittente (s) è l'host 192.168.0.213 (server) devono essere
scartati (DROP). Infatti, se riproviamo a collegarci a mail.libero.it otterremo la seguente
risposta:
[root@Alexia root]# telnet mail.libero.it 25
telnet: mail.libero.it: Temporary failure in name resolution
mail.libero.it: Host name lookup failure
[root@Alexia root]#
Facciamo ora in modo che il client si possa collegare solo alla porta 22, tutte le altre
devono essere bloccate. Colleghiamo il client al server tramite telnet usando prima la porta
22 e poi la porta 111:
C:\>telnet 192.168.0.213 22
Connessione a 192.168.0.213...
SSH-1.99-OpenSSH_3.5p1
Protocol mismatch.
Connessione all'host perduta.
C:\>
C:\>telnet 192.168.0.213 111
Connessione a 192.168.0.213...
Connessione all'host perduta
C:\>
Aggiungiamo (A) alla catena INPUT la regola
[root@Alexia root]# iptables -A INPUT -s 192.168.0.200 -p tcp
--dport ! 22 -j DROP
secondo cui vengono scartati (DROP) tutti i pacchetti tcp (p) inviati (s) dal client
attraverso tutte le porte (dport), esclusa (!) la 22.
Allora effettuando il telnet usando la porta 22, la connessione viene accettata
C:\>telnet 192.168.0.213 22
Connessione a 192.168.0.213...
SSH-1.99-OpenSSH_3.5p1
Protocol mismatch.
Connessione all'host perduta.
C:\>
mentre usando, ad esempio, la porta 111 otterremo come risposta un rifiuto:
C:\>telnet 192.168.0.213 111
Connessione a 192.168.0.213...Impossibile aprire una
connessione con l'host sulla porta 111 : Connessione non
riuscita
C:\>
Blocchiamo, ora, tutte le porte del server ad eccezione della 80. Prima apriamo un browser
e accediamo ai siti http://www.unict.it e https://www.makemu.it. Ora inseriamo la regola
[root@Alexia root]# iptables -A OUTPUT -s 192.168.0.213 -p tcp
--dport ! 80 -j DROP
A tal punto riusciremo ad aprire solo il sito http://www.unict.it. Sblocchiamo la porta 443
(https). Non possiamo semplicemente aggiungere un'altra regola in cui diciamo di accettare
i pacchetti passanti per tale porta, poiché la regola precedente prevale sulla nuova (per
l'ordine temporale). Il controllo che viene effettuato, infatti, consiste nel confrontare il
pacchetto con la prima regola inserita, solo nel caso in cui questa non sia soddisfatta si
passerà alla seconda regola. Nel nostro caso, quindi, sarà la prima regola ad essere
soddisfatta e quindi il pacchetto verrebbe scartato. Ciò che dovremmo fare, allora, è
inserire una nuova regola in cui diciamo di accettare i pacchetti attraverso la porta 443 e
specificare che tale regola ha priorità maggiore rispetto la precedente. Ciò è possibile,
perchè, implicitamente, ogni volta che inseriamo una regola in una catena, ad essa verrà
assegnato un numero (partendo da 1), allora, nel nostro caso, sapendo che alla regola
precendente corrisponde il numero 1, basterà dire che la nuova regola deve trovarsi al
primo posto nella catena:
[root@Alexia root]# iptables -I OUTPUT 1 -s 192.168.0.213 -p tcp
--dport 443 -j ACCEPT
Adesso potremmo aprire anche il sito https://www.makemu.it.
Infine, per consentire solo il traffico ICMP usiamo le regole
[root@Alexia root]# iptables –A INPUT –p ! ICMP –j DROP
[root@Alexia root]# iptables –A OUTPUT –p ! ICMP –j DROP
per negare solo il traffico ICMP, le regole
[root@Alexia root]# iptables –A OUTPUT –p ICMP –j DROP
[root@Alexia root]# iptables –A INPUT –p ICMP –j DROP
e, in modo analogo, per consentire solo il traffico TCP usiamo
[root@Alexia root]# iptables –A INPUT –p ! TCP –j DROP
[root@Alexia root]# iptables –A OUTPUT –p ! TCP –j DROP
e per negarlo
[root@Alexia root]# iptables –A INPUT –p TCP –j DROP
[root@Alexia root]# iptables –A OUTPUT –p TCP –j DROP
Relazione 6
DOCUMENTAZIONE DI IPTABLES
Iptables è un tool d'amministrazione per il filtraggio dei pacchetti IPv4.
Esso viene usato per settare, mantenere e consultare tabelle di regole di filtraggio per
pacchetti IP. Ciascuna tabella contiene un numero di catene predefinite e può anche
contenere catene definite dall'utente.
Ogni catena è una lista di regole, le quali possono soddisfare un insieme di pacchetti.
Ciascuna regola specifica cosa fare di un pacchetto che la soddisfa. Questo è detto “target”,
il quale può anche essere un salto a una catena definita dall'utente nella stessa tabella.
Una regola di firewall specifica il criterio per un pacchetto e un obiettivo (target). Se il
pacchetto non la soddisfa, la prossima regola nella catena viene esaminata; se la soddisfa,
allora la prossima regola viene specificata con il valore del target, il quale può essere il
nome di una catena definita dall'utente o uno dei valori speciali ACCEPT, DROP, QUEUE
o RETURN.
ACCEPT indica di lasciar passare il pacchetto. DROP indica di scartare il pacchetto.
QUEUE indica di passare il pacchetto all'userspace (se supportato dal kernel). RETURN
indica di interrompere l'esame dell'attuale catena e riprendere la prossima regola nella
precedente catena (chiamante). Se viene raggiunta la fine di una catena predefinita o una
regola nella catena predefinita con target RETURN viene soddisfatta, il target specificato
con la tattica (policy) della catena determina il destino del pacchetto.
TABELLE
Ci sono attualmente tre tabelle indipendenti (quali tabelle sono presenti ogni volta dipende
dalle opzioni di configurazione del kernel e da quali moduli sono presenti).
-t, --table tabella
questa opzione specifica la tabella di matching per il pacchetto su cui il comando
dovrebbe operare. Se il kernel viene configurato con moduli di caricamento
automatico, si proverà a caricare l'appropriato modulo per quella tabella se esso non
esiste già.
Le tabelle sono le seguenti:
filter Questa è la tabella di default (se l'opzione -t non viene specificata). Essa contiene le
catene predefinite INPUT (per pacchetti entranti nel box stesso), FORWARD (per
pacchetti in fase di routing attraverso il box) e OUTPUT (per pacchetti generati
localmente).
nat
Questa tabella viene consultata quando viene analizzato un pacchetto che crea una
nuova connessione. Essa consiste di tre catene predefinite: PREROUTING (per
alterare i pacchetti in entrata),
OUTPUT (per modificare pacchetti generati
localmente prima del routing) e POSTROUTING (per modificare pacchetti in
uscita).
mangle Questa tabella viene usata per particolari modifiche nei pacchetti. Fino al kernel
2.4.17 essa aveva due catene predefinite: PREROUTING (per modificare pacchetti
in entrata prima del routing) e OUTPUT (per modificare pacchetti generati
localmente prima del routing). Dal kernel 2.4.18, vengono supportate altre tre catene
predefinite: INPUT (per pacchetti in entrata nel box stesso), FORWARD (per
modificare i pacchetti in fase di routing attraverso il box) e POSTROUTING (per
alterare i pacchetti in uscita).
OPZIONI
Le opzioni usate con iptables possono essere divise in due gruppi distinti.
COMANDI
Queste opzioni specificano la particolare azione da effettuare. Solo una fra queste
può essere specificata da linea di comando, eccetto per casi particolari specificati in
seguito. Per tutte le versioni estese del comando e i nomi delle opzioni, occorre usare
le sole lettere che assicurano all'iptables di differenziarlo dalle altre opzioni.
-A, --append catena regola
Aggiunge una o più regole alla fine della catena selezionata. Quando i nomi del
mittente e/o destinatario riguardano più di un indirizzo, una regola sarà aggiunta
per ogni possibile combinazione di indirizzi.
-D, --delete catena regola
-D, --delete catena numero-regola
Cancella una o più regole dalla catena selezionata. Ci sono due versioni di questo
comando: la regola può essere specificata tramite un numero nella catena
(iniziando dall'uno per la prima regola) o da una regola da confrontare.
-I, --insert catena [numero-regola] regola
Inserisce una o più regole nella catena selezionata nella posizione specificata dal
numero dato. Quindi, se il numero della regola è 1, la regola, o le regole, saranno
inserite all'inizio della catena. Questa è la situazione di default se nessun numero
viene specificato.
-R, --replace catena numero-regola regola
Rimpiazza una regola nella catena selezionata. Se i nomi del mittente e/o
destinatario riguardano indirizzi multipli, il comando fallisce. Le regole sono
numerate iniziando dall'uno.
-L, --list [catena]
Elenca tutte le regole nella catena selezionata. Se non viene indicata nessuna
catena, tutte le catene vengono elencate. Come ogni altro comando iptables, esso
viene applicato alla tabella specificata (filter è per default), perciò le regole NAT
saranno elencate con
iptables -t nat -n -L
Da notare che spesso viene usato con l'opzione -n, questo per evitare l'operazione
di reverse lookup da parte del server DNS. E' consentito specificare anche
l'opzione -Z (zero), in tal caso la catena sarà automaticamente elencata e
cancellata. L'esatto output è influenzato dagli altri argomenti dati. Le regole
complete sono troncate se non viene usato
iptables -L -v
-F, --flush [catena]
Elimina la catena selezionata (o tutte le catene nella tabella se non viene
specificata). Questo è equivalente a cancellare le regola una per una.
-N, --new-chain catena
Crea una nuova catena definita dall'utente con il dato nome. Non devono esserci
target di quel nome.
-X, --delete-chain [catena]
Cancella la catena definita dall'utente specificata (opzionale). Non ci devono
essere riferimenti alla catena. Se esistono, si devono cancellare o rimpiazzare tali
regole prima che la catena venga cancellata. Se non viene dato alcun argomento,
si tenterà di cancellare ogni catena non predefinita nella tabella.
-E, --rename-chain old-chain new-chain
Rinomina la catena specificata con il nuovo nome fornito. Questo non ha alcun
effetto sulla struttura della tabella.
PARAMETRI
I seguenti parametri formano una regola.
-p, --protocol [!] protocollo
Il protocollo della regola o dei pacchetti da controllare. Il protocollo specificato
può essere uno fra tcp, udp, icmp o all. E' anche permesso il nome di un
protocollo da /etc/protocols.
Un “!” prima del protocollo inverte il test. Il
protocollo all combacia con tutti i protocolli ed è preso per default quando tale
opzione viene omessa.
-s, --source [!] address[/mask]
Specifica il mittente. Address può essere un nome di rete, un hostname, un
indirizzo IP di rete (con /mask), o un indirizzo IP. La mask può essere o una
maschera di rete o un numero, che specifica il numero di 1 nel lato sinistro della
maschera di rete. Quindi, una maschera pari a 24 è equivalente a 255.255.255.0.
Un “!” prima dell'indirizzo inverte il test. Il flag --src è un alias per questa
opzione.
-d, --destination [!] address[/mask]
Specifica il destinatario. La descrizione della sintassi è analoga a quella del flag s. Il flag --dst è un alias per questa opzione.
-j, --jump target
Specifica il target della regola; cioè cosa fare se il pacchetto la soddisfa. Il target
può essere una catena definita dall'utente, uno degli speciali target predefiniti i
quali decidono immediatamente il destino del pacchetto, o un'estensione. Se
questa opzione è omessa nella regola, allora il matching con la regola non avrà
alcun effetto sul destino del pacchetto, ma i contatori delle regole saranno
incrementati.
-i, --in-interface [!] nome
Nome di un'interfaccia tramite la quale un pacchetto deve essere ricevuto (solo
per pacchetti rientranti nelle catene INPUT, FORWARD e PREROUTING).
Quando viene usato il “!” prima del nome dell'interfaccia, il test viene invertito.
Se il nome dell'interfaccia finisce con un “+”, allora ogni interfaccia che inizia
con tale nome sarà soddisfatto. Se tale opzione viene omessa, ogni nome di
interfaccia soddisferà il test.
-o, --out-interface [!] name
Nome di un'interfaccia tramite la quale un pacchetto deve essere inviato (per
pacchetti rientranti nelle catene FORWARD, OUTPUT e POSTROUTING)
Quando viene usato il “!” prima del nome dell'interfaccia, il senso è invertito. Se
il nome dell'interfaccia finisce con un “+”, allora ogni interfaccia che inizia con
tale nome sarà soddisfatto. Se tale opzione viene omessa, ogni nome di interfaccia
soddisferà il test.
[!] -f, --fragment
Indica che la regola si riferisce solo al secondo e successivi frammenti del
pacchetto frammentato. Quando non c'è modo di dire la porta sorgente e di
destinazione di un tale pacchetto (o tipo ICMP), tale pacchetto non soddisferà
alcuna regola che lo specifica. Quando il “!” precede il flag “-f”, la regola
soddisferà solo il primo frammento o i pacchetti non frammentati.
ALTRE OPZIONI
Possono essere specificate anche le seguenti opzioni aggiuntive:
-v, --verbose
Verbosità dell'output. Quest'opzione fa in modo che il comando list mostri il
nome dell'interfaccia, le opzioni della regola (se ci sono) e le maschere TOS.
Vengono anche elencati i contatori di byte e di pacchetto, con il suffisso “K”, “M”
o “G” per, rispettivamente, multipli 1000, 1.000.000 e 1.000.000.000 (vedi il flag
-x per cambiare ciò). Per aggiunta, inserimento, cancellazione e rimpiazzamento,
questo comando causa la stampa di informazioni dettagliate sulla regola (o
regole).
-n, --numeric
Output numerico. Gli indirizzi IP e i numeri di porta saranno visualizzati in
formato numerico. Per default, il programma proverà a mostrarli come hostname,
nomi di rete, o servizi (quando applicabile).
-x, --exact
Numeri espansi. Mostra l'esatto valore dei contatori di pacchetto e byte, invece del
solo numero arrotondato a multipli di “K” (1000), “M” (1000K) o “G” (1000M).
Questa opzione è rilevante solo per il comando -L.
--line-numbers
Quando vengono elencate le regole, aggiunge numeri di linea all'inizio di ogni
regola, corrispondente alla posizione della regola nella catena.
ESTENSIONI
Iptables può usare moduli di confronto aggiuntivi. Essi possono essere caricati in due
modi: implicitamente, quando -p o --protocol viene specificato, o con l'opzione -m o
--match, seguito dal nome del modulo di matching; dopo di che, diventano
disponibili diverse opzioni extra, a secondo dello specifico modulo.
I seguenti sono inclusi nel pacchetto base, e la maggior parte di questi possono essere
preceduti da un “!” per invertire il test.
tcp
Queste estensioni sono caricate se --p tcp viene specificato. Esse prevedono le
seguenti opzioni:
--source-port [!] porta[:porta]
Specifica la porta (o un range di porte) mittente. Questo può essere o il nome di
un servizio o un numero di porta. Può anche essere specificato un range inclusivo,
usando il formato porta:porta. Se viene omessa la prima porta, si assume che essa
valga “0”; se viene omessa l'ultima, si assume che sia “65535”. Se la seconda
porta è più grande della prima, allora saranno scambiate. Il flag --sport è un
conveniente alias per questa opzione.
--destination-port [!] port[:port]
Specifica la porta (o un range di porte) di destinazione. Il flag --dport è un
conveniente alias per quest'opzione.
--tcp-flags [!] mask comp
E' soddisfatta quando i flag TCP sono settati come specificato. Il primo
argomento sono i flag da esaminare, scritti come una lista di elementi separati da
virgole, e il secondo argomento è una lista di flag separati da virgole che devono
essere attivati. I flag sono: SYN ACK FIN RST URG PSH ALL NONE. Quindi
il comando
iptables -A FORWARD -p tcp --tcp-flags SYN, ACK, FIN, RST SYN
soddisferà solo i pacchetti con il flag SYN attivo, e i flag ACK, FIN e RST non
attivi.
[!] --syn
Soddisfa solo i pacchetti con il bit SYN attivo e i bit ACK e FIN inattivi. Tali
pacchetti sono usati per richiedere l'inizio di una connessione TCP. E' equivalente
a --tcp-flags SYN, RST,ACK SYN. Se il flag “!” precede “--syn”, il senso
dell'opzione viene invertito.
--tcp-option [!] numero
Soddisfa se le sono attive le opzioni TCP.
--mss value[:value]
Soddisfa i pacchetti TCP SYN o SYN/ACK con il valore (o range) MSS
specificato, il quale controlla la massima dimensione del pacchetto per quella
connessione.
udp
Queste estensioni vengono caricate se specificato “--p udp”. Esse provvedono le
seguenti opzioni:
--source-port [!] porta[:porta]
Specifica la porta (o range) mittente. Vedi per i dettagli la descrizione dell'opzione
--source-port dell'estensione TCP.
--destination-port [!] porta[:porta]
Specifica la porta (o range) di destinazione. Vedi per i dettagli la descrizione
dell'opzione --destination-port dell'estensione TCP.
icmp
Questa estensione viene caricata se viene specificato “-p icmp”. Essa prevede le
seguenti opzioni:
--icmp-type [!] typename
Quest'opzione permette di specificare il tipo ICMP, che può essere un tipo
numerico o uno dei nomi dei tipi ICMP, mostrati con il comando
iptables -
p icmp -h
ESTENSIONI DEI TARGET
Iptables può usare moduli per obiettivi aggiuntivi: i seguenti sono inclusi nella
distribuzione standard.
SNAT
Questo target è valido solo nella tabella nat, nella catena POSTROUTING. Esso
indica che l'indirizzo sorgente del pacchetto deve essere modificato (così come tutti i
pacchetti seguenti in tale connessione) . Ha un tipo di opzione:
--to-source ipaddr[-ipaddr] [:port-port]
che può specificare un unico nuovo indirizzo IP mittente, un range inclusivo di
indirizzi IP, ed eventualmente, un range di porte (valido solo se nella regola è
inclusa anche l'opzione -p tcp o -p udp). Se nessun range di porte è specificato,
allora le porte sorgenti sotto 512 saranno mappate con altre porte sotto 512; quelle
fra 512 e 1023 saranno fatte corrispondere con le porte sotto 1024 e le altre porte
con quelle da 1024 in su. Quando possibile, nessuna porta verrà alterata.
DNAT
Questo target è valido solo nella tabella
nat, nella catena PREROUTING e
OUTPUT, e nelle catene definite dall'utente chiamate soltanto dalle catene
menzionate precedentemente. Esso indica che l'indirizzo di destinazione del
pacchetto deve essere modificato (così come tutti i pacchetti seguenti in tale
connessione) . Ha un tipo di opzione:
--to-destination ipaddr[-ipaddr] [:port-port]
che può specificare un unico nuovo indirizzo IP destinazione, un range inclusivo
di indirizzi IP, ed eventualmente, un range di porte (valido solo se nella regola è
inclusa anche l'opzione -p tcp o -p udp). Se nessun range di porte è specificato,
allora la porta di destinazione non sarà mai modificata.
MASQUERADE
Questo target è valido solo nella tabella nat, nella catena POSTROUTING. Esso
dovrebbe essere usato solo in connessioni con indirizzi IP assegnati dinamicamente
(dialup): se si ha un indirizzo IP statico, allora dovrebbe essere usato il target SNAT.
Il masquerade è equivalente a specificare un mapping all'indirizzo IP dell'interfaccia
da cui il pacchetto sta uscendo, ma ha anche l'effetto di far perdere la connessione
quando l'interfaccia è down. Ha un'opzione:
--to-port [port-port]
che specifica un range di porte sorgenti da usare, sovrascrivendo la selezione
effettuata per default col target SNAT. Questo è valido solo se nella regola è
specificato anche l'opzione -p tcp o -p udp.
REDIRECT
Questo target è valido solo nella tabella
nat, nella catena PREROUTING
e
OUTPUT e nelle catene definite dall'utente chiamate da quelle precedenti. Esso
altera l'indirizzo IP destinazione, inviando il pacchetto alla macchina stessa (i
pacchetti generati localmente sono mappati all'indirizzo 127.0.0.1). Ha un'opzione:
--to-port [port-port]
che specifica una (o un range) di porte destinazione da usare: senza ciò, la porta di
destinazione non viene modificata. Questo è valido solo se nella regola è
specificato anche l'opzione -p tcp o -p udp.
Relazione 7
RELAZIONE DELLO SCRIPT C
In generale, le principali operazioni che si desiderano effettuare sono:
- bloccare il traffico su una o più porte;
- acconsentire il traffico su una o più porte;
- permette ai client appartenenti alla rete interna di uscire, tramite la tecnica del
masquerade;
- ridirigere il traffico esterno su una o più porte (PAT);
- bloccare il traffico di uno o più client appartenenti alla rete locale;
- acconsentire il traffico di uno o più client appartenenti alla rete interna.
L'idea è quella di permettere all'utente di realizzare regole per iptables, specificando
pochi semplici comandi.
A tale scopo, sono state utilizzate delle “parole chiavi”, le quali individuano
un'operazione fra quelle specificate:
− BLOCKIN, per bloccare il traffico in ingresso;
− BLOCKOUT, per bloccare il traffico in uscita dei pacchetti generati localmente;
− PASSIN, per acconsentire il traffico in ingresso;
− PASSOUT, per acconsentire il traffico in uscita dei pacchetti generati localmente;
− MASK, per l'operazione di masquerade;
− PASSPAT, per ridirigere il traffico esterno su una o più porte (PAT);
− BLOCKNAT, per bloccare il traffico di uno o più client appartenenti alla rete locale;
− PASSNAT, per acconsentire il traffico di uno o più client appartenenti alla rete interna;
− DELFILTER, per cancellare tutte le regole della tabella filter;
− DELNAT, per rimuovere tutte le regole della tabella nat.
Inoltre, sono stati impostati i seguenti flag, che l'utente dovrà specificare:
− m (massima priorità), per indicare se la regola deve essere in coda alla catena (per
default) o in testa;
− c (comando), per indicare la parola chiave, quindi ciò che in realtà l'utente vuole fare;
− i (indirizzo IP), per l'indirizzo IP e l'eventuale maschera a cui applicare la regola;
− I (indirizzo IP), per l'indirizzo IP e l'eventuale maschera a cui la regola non deve
essere applicata;
− r (remote), per l'indirizzo IP della macchina remota a cui applicare la regola, da usare
solo in combinazione con BLOCKNAT e PASSNAT;
− R (remote), per l'indirizzo IP della macchina remota a cui la regola non deve essere
applicata, da usare solo in combinazione con BLOCKNAT e PASSNAT;
− n, per l'indirizzo IP di destinazione, da usare solo con la parola chiave PASSPAT;
− p (protocollo), per specificare il tipo di protocollo, tcp, udp o all (per default);
− l (luser), per specificare la porta a cui la regola deve essere applicata, la quale a sua
volta può essere una singola porta (8000), un range di porte (7000:9000) o un insieme
di porte non continue (7000, 7600);
− L (luser), per specificare la porta a cui la regola non deve essere applicata.
− d (destination port), per la porta di destinazione, solo nel caso di uso della parola
chiave PASSPAT.
Quindi l'utente dovrà indicare:
$ ./ipreg [-m] -c keyword [-i/I ipaddr[/mask]] [-n ipaddr[/mask]]
[-r/R ipaddr[/mask]] [-p protocol] [-l/L ports] [-d destPort]
Diamo, a questo punto, uno sguardo al codice.
Innanzitutto notiamo che vengono usate come variabili globali: un'array, opzreg, per
memorizzare le opzioni per iptables da passare, come secondo argomento, alla system call
execv, e nove stringhe per la memorizzazione delle opzioni indicate dall'utente, in
particolare: key, per la parola chiave; ipaddr, per l'indirizzo IP e l'eventuale maschera (per
default 0/0); remote per l'indirizzo IP remoto (default 0/0); ipdest, per l'indirizzo IP di
destinazione, da usare solo con la parola chiave PASSPAT; port, per la porta; destport, per
la porta di destinazione nel caso della ridirezione del traffico; multi, per indicare se è una
singola porta o un range di porte, e assumerà il valore 0, oppure un insieme non contiguo
di porte, e assumerà il valore 1; prot, per il tipo di protocollo, per default all; priority, per
indicare la priorità della regola, per default è minima (valore -A) e quindi la regola verrà
messa in coda alla catena.
Vengono, inoltre, usate otto funzioni aggiuntive: funerr(), interrompe il programma in caso
di errore, mostrando il suo corretto uso; ctrlKey(), ctrlPort(), ctrlDestPort(), ctrlProt(),
ctrlAddr(), ctrlRemote() e ctrlTagDestP effettuano un controllo sulla validità dell'input.
Una volta avviato il programma, viene svolto un controllo sul numero di argomenti
passati al main, infatti, come si può notare dalla specifica, l'utente dovrà almeno
specificare il comando. Quindi se il numero di argomenti è minore di due, verrà invocata
immediatamente la funzione di errore funerr(). In caso contrario, si procede con
l'acquisizione degli argomenti.
Se viene individuato il flag m (non richiede argomenti), allora verrà impostata massima
priorità alla regola, per inserirla quindi in testa alla catena, dando il valore -I alla variabile
priority. Nel caso in cui, invece, viene rilevato il flag c, p oppure d (che dovranno avere un
argomento), verrà invocata la relativa funzione di controllo, passandole come parametro
l'argomento contenuto nella variabile esterna optarg. Se viene, infine, rilevato il flag i, I, r,
R, n, l oppure L viene chiamata la corrispondente funzione di controllo, passando come
parametri l'argomento memorizzato in optarg e un carattere di controllo, il cui
funzionamento è spiegato più avanti. Inoltre, se è stato usato il flag I, R oppure L viene
assegnato il valore 1 alla variabile, rispettivamente, notI, notR o notL, per indicare l'uso di
“un'eccezione”. Ogni funzione di controllo restituirà un intero, rtn. In particolare se viene
restitutito 0, allora il controllo ha avuto successo e il programma può continuare;
altrimenti, se viene restituito 1, allora vi è qualche errore nell'input e quindi verrà invocata
la funzione di errore funerr().
Una volta superati tutti i controlli, la regola può essere effettivamente scritta. Prima di
vedere come avviene questo procedimento, vediamo come vengono effettuati i controlli
sull'input.
ctrlKey() viene invocata in seguito alla rilevazione del flag c e controlla la validità della
parola chiave confrontando semplicemente quella inserita con quelle ammissibili.
ctrlAddr() viene chiamata nel caso di uso del flag i, I, r, R oppure n e controlla la validità
dell'indirizzo IP inserito. Prima di tutto, però, viene controllato se vi è stata una ripetizione
di flag (ad esempio i ed I), tramite l'uso della funzione ctrlRepeat(), la quale riceve come
parametro il relativo flag (flagI, flagN o flagR) e restituisce 1 in caso di ripetizione, 0
altrimenti. Se tale controllo non viene superato, allora la funzione ritorna con valore 1
(errore), altrimenti procede controllando che l'indirizzo IP immesso sia valido.
Vediamo, nel dettaglio, come avviene tale controllo. In generale la stringa immessa verrà
spezzata in due parti: l'indirizzo IP e la maschera, memorizzati nelle variabili locali,
rispettivamente, IP e mask. Innanzitutto, si controlla che la lunghezza della stringa
introdotta non sia maggiore di 31 caratteri, infatti, il massimo indirizzo IP (con maschera)
valido che può essere inserito è 255.255.255.255/255.255.255.255 (31 caratteri). Superato
tale controllo, viene fatta una copia dell'intera stringa, in modo da non perdere il suo valore
nelle operazioni successive; tale copia verrà memorizzata in IP. Da quest'ultima, usando
come delimitatore “/”, viene estratto il primo token, corrispondente all'indirizzo IP, si sta
in pratica escludendo la maschera. Se IP coincide con la stringa d'input, allora vuol dire
che non è stata usata alcuna maschera, quindi a mask viene dato il valore “0”; in caso
contrario si assegnerà ad essa il secondo token. A questo punto si procede con il controllo
della validità dell'indirizzo IP, usando la funzione predefinita inet_aton().
Se l'indirizzo IP è valido, allora si continua con la verifica della validità della maschera.
Prima di tutto si controlla se essa è espressa come un indirizzo IP valido, tramite la
funzione inet_aton(); in caso negativo, si è di fronte a una maschera espressa come un
singolo numero (compresa fra 0 e 32) o essa non è valida. Se un qualunque controllo, non
è soddisfatto, allora viene visualizzato un messaggio di errore e si ritorna il valore 1,
altrimenti verrà tornato il valore 0.
ctrlProt() viene eseguita se specificato il flag p e verifica la validità del protocollo
confrontando quello inserito con quelli consentiti.
ctrlPort() viene invocata se usato il flag l oppure L. Prima di tutto, viene effettuato il
controllo per l'eventuale ripetizione di flag, così come viene fatto in ctrlAddr. In seguito, si
controlla se è stato specificato un range di porte, cercando di individuare il simbolo “:”
all'interno della stringa. In caso contrario controlla se è un insieme di numeri, cercando il
simbolo “,” e in questa situazione assegna alla variabile multi il valore 1 (il cui significato
è stato spiegato precedentemente). Altrimenti controlla se è una stringa o un singolo
numero compreso fra 1 e 65535. Se neanche questo caso viene verificato allora stampa un
messaggio di errore e restituisce 1. Se non è caduto in quest'ultimo caso, allora si
memorizza il valore della stringa in port e viene ritornato 0.
ctrlDestPort() viene invocata se usato il flag d. Esso controlla se la porta di destinazione è
una stringa o un numero compreso fra 1 e 65535. In caso contrario, visualizza un
messaggio di errore e restituisce 1.
Vediamo, finalmente, come viene costruita la regola, la quale verrà memorizzata
nell'array opzreg.
Diciamo, innanzitutto, che alla fine tale array verrà passato come secondo argomento
alla system call execv. Quindi per il corretto uso di quest'ultima, il primo elemento
dell'array deve essere il nome del comando: iptables.
Il secondo elemento sarà la priorità, quindi l'azione da effettuare (inserire in testa o
appendere in coda alla catena).
I successivi elementi dipendono dalla parola chiave specificata. Da essa infatti
dipenderà la catena, la tabella, l'indirizzo IP (se sorgente o destinazione) e il target.
Possiamo riassumere i diversi significati delle parole chiavi con la seguente tabella
Keyword
BLOCKIN
BLOCKOUT
PASSIN
PASSOUT
MASK
PASSPAT
BLOCKNAT
PASSNAT
DELFILTER
DELNAT
key
0
1
2
3
4
5
6
7
8
9
Tabella
Filter
Filter
Filter
Filter
Nat
Nat
Nat
Nat
Filter
Nat
Catena
Input
Output
Input
Output
Postrouting
Prerouting
Prerouting
Prerouting
-
Indirizzo IP
Sorgente
Destinazione
Sorgente
Destinazione
Sorgente
Sorgente - Destinazione
Sorgente - Destinazione
Sorgente - Destinazione
-
Target
DROP
DROP
ACCEPT
ACCEPT
MASQUERADE
DNAT
DROP
ACCEPT
-
Particolare attenzione deve essere data alle parole chiavi DELFILTER e DELNAT. Se
esse vengono specificate, infatti, devono essere eseguite le regole, rispettivamente,
iptables -F, per cancellare tutte le regole della tabella filter; iptables -F -t nat, per
rimuovere tutte le regole della tabella nat. Allora, in tal caso, basta usare la system call
execl con i giusti argomenti, la quale dopo l'esecuzione terminerà automaticamente il
programma.
Nel caso in cui vengano usate le altre parole chiave, invece, verranno seguite
strettamente le indicazioni della tabella precendente. In seguito verranno memorizzati
nell'array l'indicazione del protocollo (per default all) e dell'eventuale porta, facendo la
distinzione fra range di porte, che richiede il tag --dport, o un insieme di porte, il quale
richiede il tag -m per l'indicazione dell'estensione multiport e il tag --dports. Viene inoltre
controllato, nel caso di specifica di porta, che il protocollo non sia all, in tal caso, infatti, le
opzioni --dport e --dports non vengono riconosciute da iptables, in quanto parte delle
estensioni tcp e udp. Tutto ciò non è completamente vero, se usata la parola chiave
PASSPAT.
Vediamo meglio questo caso. Innanzitutto, come per gli altri casi, viene aggiornato
l'array opzreg, memorizzando la catena, la tabella, l'indirizzo IP sorgente e di destinazione,
e il target, come stabilito nella tabella. In seguito verrà memorizzato il protocollo per usare
l'opzione --to-destination, contenuta nell'estensione tcp o udp. Quindi si procede con
l'indirizzo IP di destinazione (obbligatorio) e l'eventuale porta di destinazione.
Infine, al primo elemento non assegnato dell'array verrà dato il valore NULL per il corretto
funzionamento della system call execv, la quale può essere finalmente invocata. Ad essa
viene passato come primo argomento il pathname del file eseguibile per iptables
(/sbin/iptables) e come secondo l'array appena costruito opzreg.
Come già scritto, il tipo di regola è in stretta relazione con la parola chiave inserita. La
sua omissione, infatti, determinerà un errore. Inoltre per ogni keyword, ad eccezione per
DELNAT, DELFILTER e PASSPAT, viene invocata la funzione ctrlRemote(), per
controllare se l'utente ha usato il tag r o n a secondo dei casi. In tal caso viene visualizzato
un messaggio di errore e il programma viene arrestato. Come si può, infatti, vedere nella
tabella precedente, in alcuni casi occorre specificare un solo indirizzo IP (tramite il tag i),
poiché l'altro corrisponde a quello del firewall e, inoltre, come specificato prima, il tag n
deve essere usato solo per il reindirizzamento, quindi con la parola chiave PASSPAT.
In modo analogo, per ogni parola chiave, ad eccezione per PASSPAT, viene invocata la
funzione ctrlTagDestP(), per verificare che l'utente non abbia specificato il tag d per la
specifica della porta di destinazione; ricordiamo, infatti, che tale tag è utile solo nel caso
della ridirezione del traffico (PASSPAT).
Relazione 8
Testiamo il programma, considerando la seguente situazione:
− un client appartenente a una qualche rete interna con indirizzo IP 192.168.1.2;
− un firewall con tre interfacce di rete, eth0 (192.168.0.65) per comunicare con l'esterno,
eth1 (192.168.1.1) per la comunicazione interna ed eth2 per soddisfare un servizio
richiesto dall'esterno senza accesso diretto alla rete interna (DMZ), che allo stato
attuale non viene preso in considerazione;
− un host esterno alla rete locale con indirizzo IP 192.168.0.67.
Client - Server
192.168.0.67
Firewall
192.168.0.65
eth0
eth2
192.168.1.1
eth1
Client
192.168.1.2
DMZ
Iniziamo il test accettando qualunque pacchetto generato da un host appartenente alla rete
locale e diretto ovunque e modificando l'indirizzo IP sorgente con quello del firewall,
applicando quindi la tecnica del masquerade:
[root@c0resecurity Stage]# ./ipreg -c PASSIN -i 192.168.1.0/24
[root@c0resecurity Stage]# ./ipreg -c MASK -i 192.168.1.0/24
le quali genereranno rispettivamente le regole:
iptables -A INPUT -s 192.168.1.0/24 -j ACCEPT -p all
iptables -A POSTROUTING -t nat -s 192.168.1.0/24 -j MASQUERADE
-p all
Adesso il client 192.168.1.2 può collegarsi, mediante ssh, all'host 192.168.0.67:
[root@Alexia root]# ssh -p 6699 -l alessia 192.168.0.67
[email protected]'s password:
[alessia@FlashComa alessia]$
Notiamo che finora l'host esterno ha pieno accesso al firewall. Esso, infatti, riceverà
risposta in seguito all'uso del comando ping :
[alessia@FlashComa alessia]$ ping 192.168.0.65
PING 192.168.0.65 (192.168.0.65) from 192.168.0.67 : 56(84)
bytes of data.
64 bytes from 192.168.0.65: icmp_seq=1 ttl=64 time=0.274 ms
64 bytes from 192.168.0.65: icmp_seq=2 ttl=64 time=0.127 ms
--- 192.168.0.65 ping statistics --2 packets transmitted, 2 received, 0% loss, time 999ms
rtt min/avg/max/mdev = 0.127/0.200/0.274/0.074 ms
[alessia@FlashComa alessia]$
Blocchiamo, quindi, tutto il traffico proveniente dall'host 192.168.0.67:
[root@c0resecurity Stage]# ./ipreg -c BLOCKIN -i 192.168.0.67
che creerà la regola
iptables -A INPUT -s 192.168.0.67 -j DROP -p all
A tal punto l'host esterno non riceverà più alcuna risposta:
[alessia@FlashComa alessia]$ ping 192.168.0.65
PING 192.168.0.65 (192.168.0.65) from 192.168.0.67 : 56(84)
bytes of data.
--- 192.168.0.65 ping statistics --2 packets transmitted, 0 received, 100% loss, time 1017ms
[alessia@FlashComa alessia]$
Possiamo vedere che ogni client appartenente alla rete locale può liberamente accedere
all'esterno. Il client considerato (192.168.1.2) può, infatti, ad esempio collegarsi al web
tramite un qualunque browser
Impediamo tutto ciò. Ovvero blocchiamo tutto il traffico uscente dall'interno
(192.168.1.0/24) ad eccezione di quello diretto verso il client:
[root@c0resecurity Stage]# ./ipreg -c BLOCKNAT -i 192.168.1.0/24
-R 192.168.0.67
ottenendo la regola
iptables -A PREROUTING -t nat -s 192.168.1.0/24 -d ! 192.168.0.67
-j DROP -p all
Ora il collegamento al web non avrà più successo, mentre il client riuscirà ancora all'host
esterno mediante ssh
Infine, testiamo il comando PASSPAT ridirigendo tutto il traffico proveniente dall'esterno
(192.168.0.65) attraverso la porta 8000 sul client interno 192.168.1.2 sulla porta 22. In tal
modo sarà concesso all'host esterno di comuncare con il client locale. Infatti, se proviamo
adesso tale collegamento (mediante telnet) non otterremo alcuna risposta
[alessia@FlashComa alessia]$ telnet 192.168.0.65 8000
Trying 192.168.0.65...
[alessia@FlashComa alessia]$
La mancanza di risposta, in tal caso, non è dovuta solo all'assenza del reindirizzamento, ma
anche al fatto che precedentemente avevamo bloccato tutto il traffico proveniente
dall'esterno. Per tale motivo, inseriamo prima di tutto una regola per accettare il traffico
esterno attraverso la porta 8000
[root@c0resecurity Stage]# ./ipreg
-p tcp -l 8000
-m -c PASSIN -i 192.168.0.67
ottenendo la regola
iptables -I INPUT -s 192.168.0.67 -j ACCEPT -p tcp --dport 8000
Se ora riproviamo il collegamento, noteremo che la richiesta è stata presa in
considerazione, ma è stata rifiutata:
[alessia@FlashComa alessia]$ telnet 192.168.0.65 8000
Trying 192.168.0.65...
telnet: connect to address 192.168.0.65: Connection refused
[alessia@FlashComa alessia]$
Inseriamo, quindi, la regola per il reindirizzamento:
[root@c0resecurity Stage]# ./ipreg -c PASSPAT -i 192.168.0.67
-n 192.168.0.65 -p tcp -l 8000 -r 192.168.1.2 -d 22
che produrrà la regola:
iptables -A PREROUTING -t nat -s 192.168.0.67 -d 192.168.0.65
-j DNAT -p tcp --to-destination 192.168.1.2:22
--dport 8000
Finalmente l'host esterno potrà effettuare il collegamento:
[alessia@FlashComa alessia]$ telnet 192.168.0.65 8000
Trying 192.168.0.65...
Connected to 192.168.0.65.
Escape character is '^]'.
SSH-1.99-OpenSSH_3.5p1
quit
Protocol mismatch.
Connection closed by foreign host.
[alessia@FlashComa alessia]$
Scarica

Problematiche di sicurezza in ambiente Linux