---------------------[ previous ]---[ index ]---[ next ]---------------------- ============================================================================== -------------[ BFi numero 7, anno 2 - 25/12/1999 - file 6 di 22 ]------------- ============================================================================== -[ HACKiNG ]------------------------------------------------------------------ ---[ FiLTRiAM0 iL NETFiLTER - |scacco| NetFilter e' il sistema di firewalling/filtraggio pacchetti adottato nei nuovi kernel sperimentali della serie 2.3.x . Questo nuovo sistema verra' adottato sul futuro kernel stabile 2.4 ed andra' a sostituire il vecchio ipchains. La particolarita' di questo sistema e' il concetto di hook. L'hook e' sostanzialmente un pin point all'interno dello stack tcp/ip di linux. Gli hook point non sono punti casuali, ma vengono definiti dallo stesso protocollo tcp/ip versione 4 cioe' quella corrente. Cosa succede quando un determinato pacchetto attraversa lo stack? Semplice, ogni qual volta attraversa un hook, netfilter controlla se a quel determinato hook/protocollo e' stata assegnato una funzione di gestione altrimenti passa il pacchetto avanti all'hook successivo. Come e' chiaro questo nuovo metodo di trattare i pacchetti rende la gestione del filtraggio assolutamente modulare ed indipendente. La funzione di gestione dell'hook riceve in ingresso tutto il paccketto sottoforma di una struttura skbuff: cio' consente oltre al controllo anche la modifica e la variazione della struttura stessa. Pensate quando si voglia implementare un masquerade o meglio ancora una infrastruttura di NAT, tutto risulta agevole alle funzioni di connection tracking all'interno del pacchetto stesso. Molte di queste informazioni derivano dall'howto ufficiale di netfilter che a mio parere risulta poco chiaro nella descrizione degli hook. Proviamo a correggere il tutto confrontando la documentazione corrente e il codice vero e proprio. Il sistema di hooking puo' essere rappresentato come in figura seguente: A __________ B ___________ ---------->| |----------->| | |SAN CHECK | |PRE ROUTING| |__________| |___________| | C | ____|____ / \ D ___________ / EXT ROUTE \ ----------->| | \ / | LOCAL IN | \ ________ / |___________| ________ | / \ F | / IP FORW \ <----------->| E \ / | \ ________ / | _____|_____ G | | <----------|POST ROUTE | |___________| Cerchiamo ora di analizzare il flusso di un pacchetto tcp/ip all'interno di questa struttura. Il pacchetto arriva all'interno del netfilter tramite il punto A dove avviene un sanity check riguardante il fatto che sia troncato, il checksum sia invalido e che il pacchetto non provenga da un'interfaccia in modalita' promiscua (fate riferimento agli articoli di FuSyS sull'argomento). Se supera questi check arriva al primo hook attraverso il punto B. Qui viene eseguito il check sul pre routing ( NF_IP_PRE_ROUTING ), risultera' fondamentale quando si passera' ad un sistema NAT in cui il routing in ingresso puo' essere modificato a discrezione del sistema. Tramire il punto C si arriva all'external routing: questo non e' un hook, ma semplicemente viene stabilito se il pacchetto debba essere trattenuto o se il destinatario e' esterno al sistema e quindi si debba procedere al reinstradamento del pacchetto stesso. Se il pacchetto e' locale passa tramite il punto D ad un altro hook di local input ( NF_IP_LOCAL_IN ). Tramite questo hook e' possibile effettuare il vero e proprio filtraggio del pacchetto andando a controllare i singoli flag e le autorizzazioni. Se il pacchetto supera questo primo livello di filtraggio viene eseguito un check per determinare se debba essere passato su un'altra interfaccia del sistema: questo avviene attraversando F per arrivare all'hook ip forward ( NF_IP_FORWARD ). Il pacchetto era per questa interfaccia? Ok, allora sara' diretto tramite E ad un successivo hook denonominato post routing ( NF_IP_POST_ROUTING ). A questo punto il nostro pacchetto e' pronto ad uscire dal sistema. Da non dimenticare che esiste un ulteriore hook chiamato ip local out ( NF_IP_LOCAL_OUT ) e viene chiamato in causa quando un pacchetto e' generato dall'interno. Una volta analizzato il pacchetto ovviamente deve essere possibile informare il sistema sulle azioni da intraprendere e a questo scopo NetFilter mette a disposizione 4 possibili valori di ritorno, rispettivamente: NF_ACCEPT NF_DROP NF_STOLEN NF_QUEUE L'utilizzo di questi valori di ritorno non e' intuitivo: infatti, se si specifica NF_ACCEPT, il pacchetto passera' l'hook corrente e si indirizzera' verso il successivo, mentre la se la funzione restituisce NF_DROP il pacchetto e' semplicemente scartato dal kernel senza nessuna notifica. E' possibile tuttavia utilizzare NF_STOLEN per specificare che il pacchetto non e' stato scartato, semplicemente non deve proseguire verso i prossimi hook. Questo risulta estremamente utile nel caso in cui si voglia utilizzare il NAT/masquerading, infatti e' possibile modificarlo e reinstradarlo a piacere; lo stesso discorso vale nel caso in cui si voglia agire in modo personalizzato sul pacchetto stesso. L'utilizzo di NF_QUEUE e' estremamente particolare, infatti tramite netfilter e' possibile mettere in coda i pacchetti perche' poi siano esaminati in un secondo momento sia all'interno del kernel che in user land. Anche queste nuove caratteristiche lo rendono estremanente flessibile rispetto al sistema precendente. Chi ha gia' lavorato in passato con il kernel di linux e in particolare con lo stack tcp/ip avra' notato una certo caos dovuto alla scarsissima modularita' del masquerading: in questo nuovo sistema il tutto risulta indipendente. Ora entriamo nella parte calda ed andiamo ad analizzare in dettaglio come il tutto viene implementato nella realta' usando il codice scritto da antirez come cavia: 1: /* netfilter hooks example 2: * (C) 1999 by Salvatore Sanfilippo <antirez@invece.org> 3: * This code comes under GPL version 2 4: * see http://www.opensource.org/licenses/gpl-license.html 5: * for more information. 6: * 7: * Compile with: gcc -O -c -Wall nfexample.c 8: * (note that -O is needed) 9: * Insert this module using `insmod nfexample' 10: */ 11: #define __KERNEL__ 12: #define MODULE 13: #include <linux/module.h> 14: #include <linux/kernel.h> 15: #include <linux/mm.h> 16: #include <asm/uaccess.h> 17: #include <linux/string.h> 18: #include <linux/socket.h> 19: #include <linux/ip.h> 20: #include <linux/tcp.h> 21: #include <linux/icmp.h> 22: #include <linux/netfilter.h> 23: #include <linux/netfilter_ipv4.h> 24: #include <linux/proc_fs.h> 25: #include <linux/interrupt.h> 26: #include <linux/spinlock.h> 27: #define PROC_BUFFER 4096 28: static ssize_t nfdemo_proc_read(struct file* file, char* buf, size_t count, loff_t *ppos); 29: static int info_read_proc(char* buf, char** start, off_t offs, int len); 30: static int last_read_proc(char* buf, char** start, off_t offs, int len); 31: struct nf_hook_ops input_filter; 32: struct nf_hook_ops output_filter; 33: struct proc_dir_entry *proc_nfdemo = NULL; 34: struct proc_dir_entry *proc_info = NULL; 35: struct proc_dir_entry *proc_last = NULL; 36: static rwlock_t last_lock = RW_LOCK_UNLOCKED; 37: static struct file_operations nfdemo_fops = 38: { 39: NULL, /* lseek */ 40: nfdemo_proc_read, /* read */ 41: NULL, /* write */ 42: NULL, /* readdir */ 43: NULL, /* select */ 44: NULL, /* ioctl */ 45: NULL, /* mmap */ 46: NULL, /* no special open code */ 47: NULL, /* flush */ 48: NULL, /* no special release code */ 49: NULL /* can't fsync */ 50: }; 51: static struct inode_operations nfdemo_inode = 52: { 53: &nfdemo_fops, 54: NULL, /* create */ 55: NULL, /* lookup */ 56: NULL, /* link */ 57: NULL, /* unlink */ 58: NULL, /* symlink */ 59: NULL, /* mkdir */ 60: NULL, /* rmdir */ 61: NULL, /* mknod */ 62: NULL, /* rename */ 63: NULL, /* readlink */ 64: NULL, /* follow_link */ 65: NULL, /* get_block */ 66: NULL, /* readpage */ 67: NULL, /* writepage */ 68: NULL, /* truncate */ 69: NULL, /* permission */ 70: NULL /* revalidate */ 71: }; 72: static int counter_input_dropped = 0; 73: static int counter_input_processed = 0; 74: static int counter_output_dropped = 0; 75: static int counter_output_processed = 0; 76: static char *last_packet_data = NULL; 77: /* input_handler is called when a packet hits specified hook point */ 78: unsigned int input_handler( unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out) 79: { 80: struct iphdr *ip; 81: struct tcphdr *tcp; 82: int packet_size; 83: /* inc processed counter */ 84: counter_input_processed++; 85: /* IP filtering */ 86: /* as you can see the function receives a pointer to the 87: * struct sk_buff pointer, so it's possible to replace the 88: * sk_buff with another one. This can be used to rebuild 89: * from scratch the packet (the example don't use this) */ 90: ip = (*skb)->nh.iph; 91: /* save the packet -- for /proc/nfdemo/last */ 92: write_lock_bh(&last_lock); 93: if (last_packet_data != NULL) 94: kfree(last_packet_data); 95: packet_size = ntohs(ip->tot_len); 96: last_packet_data = kmalloc(packet_size, GFP_KERNEL); 97: if (last_packet_data) 98: memcpy(last_packet_data, ip, packet_size); 99: write_unlock_bh(&last_lock); 100: /* drop IP packets with options */ 101: if (ip->ihl != 5) 102: goto drop; 103: /* TCP filtering */ 104: if (ip->protocol != 6) 105: goto accepted; 106: tcp = (struct tcphdr*)((__u32 *)ip+ip->ihl); 107: /* TCP flags sanity check */ 108: /* drops Xmas || Ymax */ 109: if (tcp->res2 != 0) 110: goto drop; 111: /* drops SYN without ACK but with others flags set */ 112: if ((tcp->syn && !tcp->ack) && (tcp->fin || tcp->rst || tcp->psh || tcp->urg)) 113: goto drop; 114: /* drops SYN/ACK with RST and/or FIN set */ 115: if ((tcp->syn && tcp->ack) && (tcp->fin || tcp->rst)) 116: goto drop; 117: /* drops TCP packets with no-sense flags (or without flags set) */ 118: if (!tcp->fin && !tcp->syn && !tcp->rst && !tcp->ack) 119: goto drop; 120: accepted: 121: return NF_ACCEPT; 122: drop: 123: counter_input_dropped++; 124: return NF_DROP; 125: } 126: /* output_handler is called when a packet hits specified hook point */ 127: unsigned int output_handler( unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out) 128: { 129: struct iphdr *ip; 130: struct tcphdr *tcp; 131: struct icmphdr *icmp; 132: /* inc processed counter */ 133: counter_output_processed++; 134: /* IP filtering */ 135: ip = (*skb)->nh.iph; 136: /* TCP filtering */ 137: if (ip->protocol != 6) 138: goto icmp_init; 139: tcp = (struct tcphdr*)((__u32 *)ip+ip->ihl); 140: icmp_init: 141: /* ICMP filtering */ 142: if (ip->protocol != 1) 143: goto accepted; 144: icmp = (struct icmphdr*)((__u32 *)ip+ip->ihl); 145: if (icmp->type == 0) /* icmp echo request */ 146: goto drop; 147: accepted: 148: return NF_ACCEPT; 149: drop: 150: counter_output_dropped++; 151: return NF_DROP; 152: } 153: int init_module(void) 154: { 155: int result; 156: /* input hook */ 157: input_filter.list.next = NULL; 158: input_filter.list.prev = NULL; 159: input_filter.hook = input_handler; 160: input_filter.flush = NULL; /* still unused */ 161: input_filter.pf = PF_INET; /* IPv4 */ 162: input_filter.hooknum = NF_IP_LOCAL_IN; 163: /* output hook */ 164: output_filter.list.next = NULL; 165: output_filter.list.prev = NULL; 166: output_filter.hook = output_handler; 167: output_filter.flush = NULL; /* still unused */ 168: output_filter.pf = PF_INET; /* IPv4 */ 169: output_filter.hooknum = NF_IP_LOCAL_OUT; 170: /* proc fs */ 171: proc_nfdemo = proc_mkdir("nfdemo", &proc_root); 172: if (!proc_nfdemo) 173: goto proc_failed; 174: proc_info = create_proc_entry("info", 00400, proc_nfdemo); 175: if (!proc_info) 176: goto proc_failed; 177: proc_info->ops = &nfdemo_inode; 178: proc_info->get_info = info_read_proc; 179: proc_last = create_proc_entry("last", 00400, proc_nfdemo); 180: if (!proc_last) 181: goto proc_failed; 182: proc_last->ops = &nfdemo_inode; 183: proc_last->get_info = last_read_proc; 184: /* hooks registration */ 185: result = nf_register_hook(&input_filter); 186: if (result) goto hook_failed; 187: result = nf_register_hook(&output_filter); 188: if (result) goto hook_failed; 189: /* OK */ 190: printk(KERN_INFO "demo netfilter module loaded\n"); 191: return 0; 192: proc_failed: 193: printk(KERN_INFO "nfdemo: error registering proc entry\n"); 194: return 1; /* error registering proc entry */ 195: hook_failed: 196: printk(KERN_INFO "nfdemo: error registering hook (%d)", result); 197: return result; /* error registering hooks */ 198: } 199: void cleanup_module(void) 200: { 201: /* unregister hooks */ 202: nf_unregister_hook(&input_filter); 203: nf_unregister_hook(&output_filter); 204: /* unregister proc fs entry */ 205: if (proc_last) 206: remove_proc_entry("last", proc_nfdemo); 207: if (proc_info) 208: remove_proc_entry("info", proc_nfdemo); 209: if (proc_nfdemo) 210: remove_proc_entry("nfdemo", &proc_root); 211: printk(KERN_INFO "demo netfilter module removed\n"); 212: } 213: /* this function handles /proc/nfdemo/info reading */ 214: static int info_read_proc(char* buf, char** start, off_t offs, int len) 215: { 216: return sprintf( buf, 217: "netfilter demo info:\n" 218: "INPUT:\n" 219: "processed datagrams: %d\n" 220: "dropped datagrams: %d\n" 221: "OUTPUT:\n" 222: "processed datagrams: %d\n" 223: "dropped datagrams: %d\n", 224: counter_input_processed, 225: counter_input_dropped, 226: counter_output_processed, 227: counter_output_dropped); 228: } 229: /* this function handles /proc/nfdemo/last reading */ 230: static int last_read_proc(char* buf, char** start, off_t offs, int len) 231: { 232: struct iphdr *ip; 233: int packet_size = 0; 234: read_lock(&last_lock); 235: if (last_packet_data) 236: { 237: ip = (struct iphdr*) last_packet_data; 238: packet_size = ntohs(ip->tot_len); 239: if (packet_size > PROC_BUFFER) 240: packet_size = PROC_BUFFER; 241: memcpy(buf, ip, packet_size); 242: } 243: read_unlock(&last_lock); 244: return packet_size; 245: } 246: /* nfdemo_proc_read() is derived from linux/net/wanrouter/wanproc.c of 247: linux 2.3.31. This function should be fixed since it can create some 248: inconguence trying to read from /proc/nfdemo/last calling read(2) more 249: than one time for packet but since this is just an example it's enought */ 250: #define min(x,y) x<y ? x : y 251: static ssize_t nfdemo_proc_read(struct file* file, char* buf, size_t count, loff_t *ppos) 252: { 253: struct inode *inode = file->f_dentry->d_inode; 254: struct proc_dir_entry* dent; 255: char* page; 256: int pos, offs, len; 257: if (count <= 0) 258: return 0; 259: dent = inode->u.generic_ip; 260: if ((dent == NULL) || (dent->get_info == NULL)) 261: return 0; 262: page = kmalloc(PROC_BUFFER, GFP_KERNEL); 263: if (page == NULL) 264: return -ENOBUFS; 265: pos = dent->get_info(page, dent->data, 0, 0); 266: offs = file->f_pos; 267: if (offs < pos) 268: { 269: len = min(pos - offs, count); 270: if(copy_to_user(buf, (page + offs), len)) 271: return -EFAULT; 272: file->f_pos += len; 273: } 274: else 275: len = 0; 276: kfree(page); 277: return len; 278: } Analizziamo in dettaglio il codice per capire come si fa a costruirsi un mini firewall in modo abbastanza semplice. Partiamo dalla parte interessante cioe' il filtro diretto sugli hook ovvero la funzione di gestione dell'hook NF_IP_LOCAL_IN. Notate alla riga 78 che in input a questa funzione abbiamo la struttura skb del pacchetto in ingresso. Alla riga 90 andiamo ad estrarre da skb l'ip header del pacchetto per poterlo analizzare separatamente. Dalla riga 106 alla riga 119 viene eseguito un controllo TCP contro le normali vulnerabilita' come Xmas e Ymas, opzioni illegali e stati erronei. In questo esempio antirez ha usato i valori di ritorno predefiniti di netfilter senza modificare la struttura skb, quindi avremo due possibilita' NF_ACCEPT ed NF_DROP. Allo stesso modo alla riga 127 viene dichiarata la procedura di gestione dell'hook NF_IP_LOCAL_OUT. Come precedentemente viene estratto l'ip header ed analizzata la parte tcp, notate che alla riga 147 viene eseguito un check sul protocollo ICMP per evitare ping flood. Alla riga 153 viene inizializzato il module e vengono riempite due strutture, rispettivamente input_filter(157-162) ed output_filter(164-169). Notate in particolare le righe 162 e 169 dove viene dichiarato l'hook di riferimento e le righe 159 - 166 dove vengono rispettivamente dichiarate le funzioni di handling (gestione). Questo e' un esempio semplicissimo su come si possa realizzare un firewall sotto i futuri kernel. Con questo passo e chiudo ricordandovi di provare il SINUS Firewall, lo potete trovare all'url: http://www.sinusfirewall.org . Per qualunque domanda potete scrivermi a scacco@s0ftpj.org . ============================================================================== ---------------------------------[ EOF 6/22 ]--------------------------------- ============================================================================== ---------------------[ previous ]---[ index ]---[ next ]----------------------