Immer wieder werden Umsteiger von anderen Betriebssystemen bei ihren ersten Kontakten zu Ubuntu-Anwendern damit konfrontiert, dass sie bei Ubuntu keine Personal Firewalls benötigen. Lassen sich die meisten von ihnen noch relativ leicht davon überzeugen, dass eine gepflegte Ubuntu-Installation nach außen hin auch ohne Paketfilter genau so wenig Angriffsfläche bietet, so kommt doch immer wieder der Wunsch auf, den Internetzugriff lokal laufender Applikationen zu kontrollieren.
Auch dieser Wunsch ist auf einem Ubuntu-System in den meisten Fällen nicht nötig - zumindest braucht man dafür keine "Personal Firewall" mit bunten Pop-Up-Fenstern, unterschiedlichen "Warnleveln" und was man sonst noch so aus der schillernden Welt der Sicherheitssoftware-Industrie kennt.
Solche Software kann einen vor ernsthafter Schadsoftware sowieso nicht ernsthaft schützen. (Siehe weiterführende Links.) In legitimer Open-Source-Software lassen sich Spywarefunktionen dagegen nicht verstecken, und sie könnten quasi sofort von den Distributoren herausgepatcht werden. Bleibt also nur noch jene Handvoll Closed-Source-Applikationen, die auch unter Linux Verwendung finden, deren Hersteller aber wenig Wert auf die Privatsphäre der Nutzer legen. Hier setzt anfd an.
Wichtig ist zu begreifen, dass anfd keine Sicherheitssoftware ist, sondern Datenschutzsoftware. Der beste Schutz gegen Trojaner und verwandte Software ist immer noch, keine Software aus unbekannter Quelle zu installieren. Anfd kümmert sich nicht um Software, die der Benutzer unbewusst installiert, sondern blockiert bestimmte, aus Benutzersicht unerwünschte Internetverbindungen jener Software, deren Primärfunktionen er gerne trotzdem nutzen will.
Dabei ist es möglich, einzelnen Applikationen selektiv den Internetzugang komplett zu verweigern, auf bestimmte Adressbereiche zu beschränken, oder nur bestimmte Bereiche (z.B. das Netz des Herstellers) zu blockieren.
Da die Zuordnung bestimmter Internetverbindungen zu einzelnen Applikationen innerhalb von iptables / netfilter nicht problemlos möglich ist - und die rudimentäre Unterstützung dafür im Ubuntu-Kernel deaktiviert ist - findet die Filterung mittels anfd im sog. Userspace, also außerhalb des Kernels, statt. Die Pakete werden mittels des IP_Queue-Mechanismus an den anfd-Daemon übertragen, der dann über die Legitimation der betreffenden Verbindung entscheidet. Standardmäßig erstellt anfd eine passende iptables-Regel, die auf einem System ohne (auf dem Desktop meist sowieso überflüssige) "Firewall" funktioniert.
Wer anfd parallel mit anderen Filterregeln einsetzen will, kann über die Kommandozeilenoption -i
eine passendere iptables-Regel definieren, oder mit der Option -x
deutlich machen, dass er sich lieber selber um die Erstellung der QUEUE-Regel(n) kümmern will.
Folgenden Pakete müssen vor der Installation von anfd installiert werden [1]:
libiptables-ipv4-ipqueue-perl (universe, [2])
libnet-cidr-perl (universe)
libnet-rawip-perl (universe)
Das Skript selber kopiert man einfach an eine geeignete Stelle im Pfad, z.B. nach /usr/local/sbin.
Wenn man anfd dauerhaft verwenden will, sollte man noch ein Startskript /etc/init.d/anfd erstellen [4] und beim Systemstart aktivieren [5].
Anfd selber wird über Kommandozeilenoptionen und eine Konfigurationsdatei (standardmäßig /etc/anfd.conf) gesteuert:
anfd [-D] [-i 'iptables command'] [-x] [-c configfile] [-p pidfile] anfd -k [-p pidfile] anfd (-h|-?)
-h
oder -?: Hilfe. Gibt eine kurze Syntax-Beschreibung aus.
-k
: Stoppt einen laufenden anfd-Prozess.
-D
: "Debug-Modus". Anfd löst sich in diesem Fall nicht vom Terminal, sondern gibt detaillierte Angaben zu jedem geprüften Datenpaket aus. Das kann benutzt werden, um die Aktivitäten bestimmter Software zu verfolgen und die Konfiguration zu verfeinern.
-c file
: Alternative Konfigurationsdatei. Standard ist /etc/anfd.conf
-p file
: Alternative Pid-Datei. In der Pid-Datei wird die Prozess-ID des laufenden anfd hinterlegt. Das verhindert unabsichtliche Mehrfachstarts des Daemons und wird von anfd -k benutzt. Standard ist /var/run/anfd.pid.
-i command
: Hiermit kann man ein alternatives iptables-Kommando wählen, mit dem die Datenpakete an den anfd-Daemon geschickt werden. Den Standard erhält man mit anfd -h.
-x
: Der Daemon erzeugt keine iptables-Regel beim Start. Die korrekte Übergabe der Datenpakete an den anfd-Daemon wird vom Administrator anderweitig geregelt.
Die Konfigurationsdatei (standardmäßig /etc/anfd.conf) ist zeilenweise aufgebaut. Leerzeilen werden ignoriert, ebenso alles zwischen dem #-Zeichen und dem Zeilenende. Ansonsten bezeichnet jede Zeile die Regel(n) für eine bestimmte Applikation. Die einzelnen Bezeichner werden mit Leerzeichen getrennt.
Jede Zeile beginnt mit der Definition der Applikation, für die die Regel gelten soll. Leerzeichen sind innerhalb dieser Definition nicht erlaubt. Es gibt drei Möglichkeiten, diese anzugeben:
vollständiger Pfad: Wird ein vollständiger Pfad (beginnend mit einem /) angegeben, so gilt die Regel erwartungsgemäß genau für das bezeichnete Programm.
Programmname: Der einfache Name einer ausführbaren Datei bezeichnet eine Regel, die für alle Dateien dieses Namens gilt, unabhängig von der Position im Dateisystem.
^Kommandozeile: Diese Regel trifft zu, wenn eines der Kommandozeilenargumente des Programms mit der durch das Zirkumflex-Zeichen gekennzeichneten Zeichenkette übereinstimmt. Das ist vor allem sinnvoll, um Skripte in Interpretersprachen zuverlässig unterscheiden zu können.
Der Rest jeder Zeile beinhaltet beliebig viele Regeln. Folgende Formen sind erlaubt:
*: Jeglicher Internetzugriff wird dieser Applikation verweigert.
IP-Adresse (z.B. 10.10.10.10): Zugriff auf eine bestimmte IP-Adresse wird verweigert.
Netzwerk-Adresse (z.B. 10.0.0.0/8): Zugriff auf ein komplettes Netzwerk wird verweigert.
IP-Bereich (z.B. 10.0.0.1-10.0.2.254): Zugriff auf alle IPs des angegebenen Bereichs wird verweigert.
! (z.B. !10.0.0.14): Kehrt den Sinn einer der drei o.a. Regeln um, d.h. der Zugriff auf bezeichnete IP, Netzwerk oder IP-Bereich wird erlaubt. Ein Leerzeichen zwischen dem ! und der IP ist nicht gestattet.
Wenn auf ein Datenpaket sowohl eine Verbots- als auch eine Erlaubnis-Regel passt, wird die Verbindung erlaubt, unabhängig von der Reihenfolge der Regeln.
Der IP_Queue-Mechanismus kennt leider nur die Möglichkeiten DROP und ACCEPT, um mit Paketen zu verfahren. Damit gedroppte Verbindungen nicht bis zum Timeout hängen bleiben, erzeugt anfd selber ein passendes network-unreachable-Paket. Dieses wird leider vom Connection Tracking als INVALID angesehen. Man sollte also auf der Loopback-Schnittstelle keine Regel haben, die diese Pakete blockiert.
Wenn sich anfd außerplanmäßig beendet, wird die Aufräumroutine nicht durchgeführt und die iptables-QUEUE-Regel muss von Hand gelöscht werden. Das sollte aber eigentlich nicht vorkommen.
IP_Queue ist veraltet. Es gibt einen neueren Mechanismus namens nfnetlink_queue in aktuellen Kerneln. Dieser ist allerdings sehr neu, so gut wie gar nicht dokumentiert und es gibt noch kein passendes Perl-Modul. Deswegen erschien es sinnvoll, vorerst auf den alten Mechanismus zu setzen.
Hier das Skript herunterladen.
#!/usr/bin/perl -w # # anfd - Ain't no firewall daemon # # Autoren: ostcar und otzenpunk # Homepage: http://wiki.UbuntuUsers.de/Skripte/anfd # Lizenz: Gnu GPL Version 2 oder höher # # Changelog: # 18.11.2006: v0.1 # initial release use strict; use POSIX; use Getopt::Std; use IPTables::IPv4::IPQueue qw(:constants); use Net::CIDR qw(:all); use Net::RawIP; use constant VERSION =>"0.1"; my @IP_PROTO ; $IP_PROTO[0]='IP'; # Dummy protocol for IP $IP_PROTO[1]='ICMP'; # Internet Control Message Protocol $IP_PROTO[2]='IGMP'; # Internet Group Management Protocol $IP_PROTO[4]='IPIP'; # IP in IP encapsulation $IP_PROTO[6]='TCP'; # Transmission Control Protocol $IP_PROTO[17]='UDP'; # User Datagram Protocol my %opts; my (%allow,%forbidden); my (%use_cmdline); my @ipt_command = qw(/sbin/iptables -I OUTPUT -m state --state NEW -j QUEUE); my $ipt_done; # wichtig für cleanup my $default_config = '/etc/anfd.conf'; my $pid_file = '/var/run/anfd.pid'; getopts('kph?Dxc:i:', \%opts); $opts{'h'} || $opts{'?'} and usage(); $< and info("You must be root to use $0.",4); $opts{'k'} and killUs(); # We'll never come back from there sub cleanup { #in dieser funktion kein info() verwenden, da es zu einer endlosschleife führen könnte print ("\tReceiving Term-Signal. Cleaning up.\n"); if ($ipt_done) { $ipt_command[1] = '-D'; print("\tResetting iptables.\n"); system(@ipt_command) == 0 or print("\t\tWarning: iptables failed: $?\n"); } if (-e $pid_file) { print("\tRemoving pid file.\n"); unlink $pid_file or print("\t\tWarning: Can't remove pid file $pid_file.\n\n"); } print("Exiting.\n"); exit; } $SIG{INT}=$SIG{TERM}=$SIG{HUP}=\&cleanup; $pid_file = $opts{'p'} if $opts{'p'}; @ipt_command = split " ", $opts{'i'} if $opts{'i'}; (-e $pid_file) && die("Pid-File already exists. Maybe another anfd running?\n"); $opts{D} ? info("Starte anfd v".VERSION,1) : print "Starte anfd v".VERSION."\n"; readConfigFile($opts{c} || $default_config) or info("Empty configuration file", $opts{D} ? 0 : 4); unless ($opts{'x'}) { system('modprobe', 'ip_queue'); system(@ipt_command) == 0 or info("iptables failed: $?",4); $ipt_done = 1; } my($ipversion, $proto,$src_ip ,$dest_ip,$dest_port, $src_port,$proc,$cmdline); my $queue = new IPTables::IPv4::IPQueue(copy_mode => IPQ_COPY_PACKET, copy_range => 2048) or info(IPTables::IPv4::IPQueue->errstr,4); # "Daemonisieren" - vom Terminal abkoppeln # Perl-Kochbuch: (Seite 754ff) unless ($opts{'D'}) { my $pid=fork; exit if $pid; info("Error: Can't fork(): $!",4)unless defined($pid); writePid(); for my $handle (*STDIN, *STDOUT, *STDERR){ open($handle, "+<", "/dev/null") or info("Could not redirect $handle to /dev/null: $!",4); } POSIX::setsid() or info("Could not start new session: $!",4); } else { writePid(); } # Endlosschleife: while(1) { # Paket aus der Queue holen my $msg = $queue->get_message(); if (!defined $msg) { next if IPTables::IPv4::IPQueue->errstr eq 'Timeout'; info(IPTables::IPv4::IPQueue->errstr,4); } info("Packet arrived",1); info("parse",1); ($ipversion, $proto, $src_ip, $dest_ip, $src_port, $dest_port)=ripPayload($msg->payload()) if $msg->data_len(); $proto=$IP_PROTO[$proto] || 'Unknown'; info("IP-Version: $ipversion"); info("Protocol: $proto"); info("Dest-IP: $dest_ip"); info("Dest-Port: $dest_port"); if ($ipversion == 4) { if ($proto eq 'TCP' || $proto eq 'UDP') { ($proc,$cmdline)=getProcName($src_port,$proto); if ($proc) { info ("Path: $proc"); info ("cmdLine: $cmdline"); info ("",2); info ("checking rules...",1); if(canPass($proc,$cmdline,$dest_ip)){ info ("can pass"); $queue->set_verdict($msg->packet_id, NF_ACCEPT); } else { info("can not pass"); sendReject($msg->payload(),$src_ip); $queue->set_verdict($msg->packet_id, NF_DROP); } info("",2); info("Done",2); next; } else { # no process id info("can not spy out the name",3); } } else { # no tcp/udp info("$proto not supported with anfd v".VERSION,3); } } else { # IPv6 et al. info("IPv$ipversion not supported with anfd v".VERSION,3); } info("",3); # accept evt. unknown so wont break anything $queue->set_verdict($msg->packet_id, NF_ACCEPT); } #function zu einladen der conf datei sub readConfigFile { # readconfigfile($configfile) my $file = shift; my $inuse; info("Load config file",1); open(CF,"$file") or info("Warning: can not open $file",4); while(<CF>){ my($proc, @ip, @allow, @forbidden); next if /^\s*(?:$|#)/; # Bei leerer Zeile gar nicht erst weiter umwandeln s/\s*#.*//; # Löscht von # bis Zeilenende ($proc, @ip)=split(); #alle wörter der Zeile durch whitespaces trennen info("Config error: $_", 4) if !defined($ip[0]); # fehler, wenn nicht beide argumente vorhanden sind $proc =~ s/^\^// && ( $use_cmdline{$proc} = 1 ); # bei ^ am anfang cmdline benutzen for (@ip) { my @ipranges; my @h; if (s/^!//) { info("$proc can pass to $_"); @ipranges = ((@h = validIPs($_)) ? range2cidr(@h) : ()) or info("Config error: $_", 4); push @allow, @ipranges; } elsif (/^\*$/) { info("$proc can not pass by default"); push @forbidden, '0.0.0.0/0'; $inuse=1; } else { info("$proc can not pass to $_"); @ipranges = ((@h = validIPs($_)) ? range2cidr(@h) : ()) or info("Config error: $_", 4); push @forbidden, @ipranges; $inuse=1; } } $forbidden{$proc}=\@forbidden; $allow{$proc}=\@allow; } info("Done",2); return $inuse; } sub validIPs { for (@_) { next if cidrvalidate($_); if ( /^([0-9.]+)\/([0-9]+)$/ ) { next if cidrvalidate($1) && $2 <= 32; } if ( /^([0-9.]+)-([0-9.]+)$/ ) { next if cidrvalidate($1) && cidrvalidate($2); } return 0; # wenn es in kein Schema passt, ist es falsch } return (@_); # wenn wir hier ankommen, ist alles in Ordnung } #function zum Regel abgleichen sub canPass{ # $bool = can_pass($programmname, $cmdline, $ipadresse) # return 1: kann passieren # return 0: blockieren my $proc=shift; my $cmdline=shift; my $ip=shift; my(@forbidden,@allow,@cmd); #alle argumente überprüfen, und auch des program ohne pfad @cmd=split('\0',$cmdline); for (@cmd){ if(defined($use_cmdline{$_})||defined($forbidden{$_})||defined($allow{$_})){ @forbidden=@{$forbidden{$_}}; @allow=@{$allow{$_}}; } } @forbidden=@{$forbidden{$proc}} if(defined($forbidden{$proc})); @allow=@{$allow{$proc}} if(defined($allow{$proc})); # Regel: Verbieten, wenn in @forbidden, aber nicht in @allow if(@forbidden && cidrlookup($ip,@forbidden)){ if(@allow && cidrlookup($ip,@allow)){ return 1; }else{ return 0; } }else{ return 1; } } #nimmt einen quellport und gibt die inode zurück bzw 0 für fehler sub srcPort2inode{ # $inode = quellporttoinode($quellport,$protocol) # Fehler: return 0; my $src_port=shift; my $protocol=lc(shift); open(FILE, "/proc/net/$protocol") or return 0; while(<FILE>) { my @cline=split; next if ($cline[9]=~/uid/); my $port = hex((split(":",$cline[1]))[1]); if($port==$src_port){ close(FILE); return $cline[9]; } } close(FILE); return 0; } #nimmt eine inode und gibt eine prog nr zurück sub inode2procNr{ # $procnr = inodetoprocnr($inode); # bei Fehler: return 0; my $inode=shift; return 0 unless $inode; my($proc,$ports,$dev,$ino,$link); opendir (PROC, "/proc") or return 0; for $proc (readdir(PROC)) { # Infos zu jedem laufenden Programm liefert /proc # Wir müssen alle testen, bis eins passt. next if(!($proc=~/[0-9]+/) ); # Nach PIDs suchen. if(!opendir(PORTS,"/proc/$proc/fd")) { # fd-Ordner öffnen closedir PORTS; next; # nächstes Programm, wenn Öffnen fehlschlägt } for $ports (readdir(PORTS)) { #in jedem laufenden program die geöffneten dateien durchsuchen next if(!($ports=~/[0-9]+/)); #muss zahlen beinhalten $link=readlink("/proc/$proc/fd/$ports"); #zu der datei gehn, auf welches die nummer zeigt ($dev,$ino)=($link=~/^(socket|\[[0-9a-fA-F]*\]):\[?([0-9]*)\]?$/); if(defined($ino)&& defined($dev) && $ino==$inode && ($dev eq "[0000]" || $dev eq "socket")){ closedir PORTS; closedir PROC; return $proc; } } closedir PORTS; } closedir PROC; return 0; } #nimmt einen srcPort und gibt den programnamen und die cmdline aus sub getProcName{ # ($pfad, $cmdline) = getprocname($quellport,$protocol); # Fehler: return 0; my $procnr=inode2procNr(srcPort2inode(@_)); return 0 unless $procnr; my($pfad,$cmdline,@cmdline); $pfad=readlink("/proc/$procnr/exe"); open(CMDLINE,"/proc/$procnr/cmdline") or return 0; $cmdline=<CMDLINE>; @cmdline=split(" ",$cmdline); $cmdline[0]=~s/^\/.*\///; $cmdline=join(" ",@cmdline); close(CMDLINE); return $pfad, $cmdline; } # Nimmt ein TCP/IP-Packet und liefert IP-Version (norm. 4), Protokoll (TCP/UDP als Nummer) IPs und Ports zurück sub ripPayload { my $payload = shift; my ($version, $proto, $src_ip, $dest_ip, $src_port, $dest_port); my (@src_ip, @dest_ip); # IP-Header auseinanderpflücken: ($version, undef, $proto, undef, @src_ip[0..3], @dest_ip[0..3], $src_port, $dest_port) = unpack("C1a8C1a2C8n1n1", $payload); $version >>= 4; # IPv steht in linken vier Bits $src_ip = join ".", @src_ip; $dest_ip = join ".", @dest_ip; return ($version, $proto, $src_ip, $dest_ip, $src_port, $dest_port); } # IP_QUEUE kennt nur ACCEPT und DROP. Deswegen konstruieren wir unser eigenes Reject-Paket sub sendReject { my $payload = unpack("a28", shift); # Anfang des Originalpakets wird in ICMP verpackt my $ip = shift; Net::RawIP->new({ip => {saddr => $ip, daddr => $ip}, icmp => {type => 3, code => 0, data => $payload} })->send(); # network unreachable } INIT { my $ebene = 0; sub info { my $info = shift; my $command = shift || 0; unless ($opts{D}) { if ($command == 4) { print "$info\n"; cleanup(); } return; } if ($command==1){ #neue ebene print "\t" x $ebene.$info.": \n"; $ebene++; }elsif ($command==2){ #ebene runter $ebene--; print "\t" x $ebene .$info."\n" if $info; }elsif($command==3){ #ebene runter mit fehler $ebene--; print "\t" x $ebene."Error".($info?": $info":"")."\n"; }elsif($command==4){ #alle ebenen runter mit fehler und ende $ebene--; for(;$ebene>0;$ebene--){ print "\t" x $ebene."Critical Error".($info?": $info":"")."\n"; } cleanup(); }else{ print "\t" x $ebene.$info."\n" } } } sub usage { my $ipt = join " ", @ipt_command; print <<EOT; $0 - Ain't no firewall daemon. anfd [-D] [-i 'iptables command'] [-x] [-c configfile] [-p pidfile] anfd -k [-p pidfile] anfd (-h|-?) Anfd is a userspace daemon that uses the netfilter-ip_queue mechanism to hinder specific software from "phoning home". It is not security software but privacy software. Options: -h, -? : Print this help message. -D : Debug mode - don't detach from terminal and print detailed infos -i 'command': Use this iptables command to insert the QUEUE rule. Default: $ipt -x : Dont insert any iptables rule. Admin will take care of that herself. -c file : Use this config file. Default: $default_config -p pidfile : Use this pid file. Default: $pid_file -k : Kill running anfd process. EOT exit 1; } sub killUs { open PID, "< $pid_file" or die("Can't open pid file. Either there is no running anfd process or you'll have to kill running anfd processes yourself.\n"); my $pid = <PID>; close PID; ($pid) = split '\D+', $pid; $pid or die("No pid found in $pid_file. You'll have to kill running anfd processes yourself.\n"); unless (kill 'TERM', $pid) { print("No process $pid. You'll have to kill running anfd processes yourself.\n"); unlink $pid_file or print "Can't delete $pid_file.\n"; } print("Killed anfd.\n"); exit; } sub writePid { if (-e $pid_file) { info("There seems to be another anfd process running. Use anfd -k to get rid of it.",4); } else { open PID, "> $pid_file" or info("Can not write pid file.\n",4); print PID $$; close PID; } }
Diese Revision wurde am 1. Dezember 2014 18:46 von rsadmin erstellt.