Mit einer Datenbank, die IP-Adressen lokalisiert und dem Google Charts Service kann man sehen, aus welchen geographischen Regionen die Linkspammer einer Webseite herkommen.
Manchmal wäre es schon sehr befriedigend, wie in dem amerikanischen Werbespot der Firma Snickers [2] das Büro eines Spammers oder Telemarketers aufzuspüren, tatsächlich dort hinzufahren und seiner Wut freien Lauf zu lassen. Leider ist dies aus legalen und logistischen Gründen oft nicht möglich. Außerdem erledigen die Drecksarbeit nicht die Gauner selbst, sondern infizierte Botnetze, aber es wäre durchaus interessant, einmal graphisch darzustellen, aus welchen geographischen Regionen die meisten Spamaktivitäten so kommen.
Das Internet ist die ideale Plattform für anonyme Gaunereien, aber eine Spur hinterlassen die Verbrecher dennoch: Jeder eingehende Request führt die IP-Addresse des Senders mit sich (Abbildung 1). Diese lässt sich zwar auch spoofen, aber das ist nicht so einfach und den meisten Spammern zu umständlich.
Abbildung 1: Link-Spammer hinterlassen im Access-Log des Webservers ihre IP-Adresse. |
Abbildung 2: Ein Reverse-DNS-Lookup bringt oft die einer IP-Addresse zugeordnete Domain zum Vorschein. |
01 #!/usr/local/bin/perl -w 02 use strict; 03 use Socket; 04 05 my $host = $ARGV[0] or 06 die "usage: $0 ipaddr"; 07 08 print reverse_lookup($host) || "unknown", 09 "\n"; 10 11 ########################################### 12 sub reverse_lookup { 13 ########################################### 14 my ($ip) = inet_aton $_[0]; 15 16 return (gethostbyaddr($ip, AF_INET))[0]; 17 }
Das DNS-System, das Hostnamen IP-Adressen zuweist, bringt oft auch die
umgekehrte Zuordnung zustande. Ein sogenannter DNS-Reverse-Lookup nimmt
eine IP-Adresse entgegen, und falls der Service Provider des Gauners
alles ordnungsgemäss aufgesetzt hat, gibt das Skript in Listing
revlookup
wie in Abbildung 2 gezeigt einen Hostnamen aus, aus dem
sich oft der Provider ermitteln lässt. Abbildung 2 zeigt, dass die beim
Spammen erwischte IP 69.162.110.146 dem ISP lstn.net gehört und eine
freundliche Email an deren Webmaster mit Angabe der IP, der Uhrzeit
(wichtig, da diese IPs unter Umständen dynamisch vergeben werden), bringt
mit etwas Glück den Spammer zum Schweigen.
Die Funktion inet_aton()
aus dem Modul Socket nimmt eine IP-Adresse
in String-Darstellung (``x.x.x.x'') entgegen und gibt eine Datenstruktur
für einen folgenden Aufruf der Perl-Funktion gethostbyaddr()
zurück.
Letztere führt den DNS-Reverse-Lookup durch (AF_INET gibt an, dass es
sich um eine IPv4-Adresse handelt) und liefert im Erfolgsfall einen
String mit dem Hostnamen, im Fehlerfall undef
. Der Vorgang kann allerdings
bis zu einigen Sekunden dauern, je nachdem, wie beschäftigt der
genutzte DNS-Server gerade ist und wieviele seiner Kollegen er zur Beantwortung
der Frage konsultieren muss.
Die Kommandozeilen-Utility whois
funktioniert nicht nur mit Domains,
sondern verkraftet ebenfalls IP-Adressen als Argumente. Abbildung 3 zeigt,
dass der Provider ``Limestone Networks'' alles sehr ordentlich registriert
hat und auch gleich eine Email-Adresse für Beschwerden angibt, an die sich
der bespammte Webmaster wenden kann. Das Ganze geht auch programmatisch
in Perl, beispielsweise mit dem CPAN-Modul Net::Whois::Raw, doch erfolgt
der Lookup über die Server der Firma Network Solutions, die nach etwa
100 Lookups in kurzer Folge den Zugang sperrt. Eine ganze Access-Log-Datei
damit zu durchforsten ist damit also, auch wenn man bereits erfolgte
Abfragen in einem Cache speichert, nicht möglich.
Abbildung 3: Der Whois-Eintrag für die ertappte IP-Adresse zeigt die Daten des Internet-Providers des Spammers. |
Viele Spammer arbeiten jedoch mit IP-Adressen, die keinen Reverse-Eintrag im DNS-System aufweisen. Doch auch dann ist eine gewisse Lokalisierung möglich, denn IP-Adressen werden in Blocks an Service-Provider vergeben und es existieren Datenbanken, die man sich herunterladen kann und die dann zu einer gegebenen IP-Adresse blitzschnell deren geographische Lage ermittelt.
Die Firma MaxMind bietet unter [3] eine Datenbankdatei an, die man für nichtkommerzielle Zwecke kostenlos nutzen kann. Die genauen Lizensbedingungen liegen im gleichen Ordner wie die Datenbank selbst. Das CPAN-Modul IP::Country::MaxMind stellt eine passende API zur Verfügung, damit man nicht mit den Binärdaten direkt herumfutzeln muss. Die gespeicherten IP-Zuweisungen ändern sich nur sehr langsam, sodass Updates nur alle paar Monate notwendig sind.
Nach der Installation des Moduls, das auch ein weiteres CPAN-Mudul
namens Geo::IP::PurePerl verlangt, liest der Konstruktor open()
die angegebene lokale Datenbank ein und die Methode inet_atocc()
liefert zu einer
IP-Adresse den Ländercode (zum Beispiel DE für Deutschland) zurück.
Für eine graphische Darstellung dieser Codes auf einer Weltkarte bietet sich die Google-Charts-API [4] an. Spielt man dem Google-Server die Wertepaare per URL zu, antwortet dieser mit einer Bilddatei im PNG-Format. Das Datenformat der Wertepaare ist etwas gewöhnungsbedürftig, denn auch mittelgroße Datenmengen müssen in das stark eingeschränkte Platzangebot einer URL mit Query-Parametern passen.
Das einfachste Datenformat, das sogenannte ``Simple Encoding'' lässt über das API nur Werte von 0 bis 61 zu, kodiert mit 'A-Z' (0-25), 'a-z' (26-51) und '0-9' (52-61).
Weist man beispielsweise Deutschland den Wert 23 zu, den USA den Wert
3 und Japan den Wert 60, kodiert man die Ländercodes im URL-Parameter
chld
mit ``DEUSJP'' ('DE', 'US', 'JP' ohne Leerzeichen aneinandergereiht)
und die Werte in
chd
mit ``s:XD8'' ('s'=> simple encoding, 'X'=>23, 'D'=>3, '8'=>60).
Das Skript in Listing
spam2geo
fasst alles bisher gezeigte zusammen. Es analysiert
die Datei access.log
eines von Linkspam bedrohten Apache-Servers. Das
CPAN-Modul ApacheLog::Parser stellt die Funktion parse_line_to_hash
bereit, die das Format von access.log
versteht und die Einzelfelder
in einem Hash zurückliefert. Unter dem Eintrag client
steht jeweils
die IP-Adresse des Spammers, ein Aufruf der Methode inet_atocc
in Zeile
27 gibt den zweibuchstabigen Ländercode zurück, falls die
Datenbank ihn findet.
01 #!/usr/local/bin/perl -w 02 use strict; 03 use LWP::UserAgent; 04 use URI::URL; 05 use List::Util qw(max min); 06 07 use IP::Country::MaxMind; 08 use ApacheLog::Parser 09 qw(parse_line_to_hash); 10 11 my $gi = 12 IP::Country::MaxMind->open("GeoIP.dat"); 13 14 my %by_country; 15 16 open LOG, "<access.log" or 17 die "Can't open access.log ($!)"; 18 19 while(<LOG>) { 20 chomp; 21 my %fields = parse_line_to_hash $_; 22 23 # only proceed if forum post 24 next if $fields{file} !~ /posting/; 25 26 my $country = 27 $gi->inet_atocc( $fields{client} ); 28 29 if(defined $country) { 30 $by_country{ $country }++; 31 } 32 } 33 34 close LOG; 35 36 # Convert values to Google format 37 my @SYMBOLS = ("A" .. "Z", 38 "a" .. "z", 0 .. 9); 39 40 my $max = max values %by_country; 41 my $min = min values %by_country; 42 43 for my $country (keys %by_country) { 44 45 my $val = $by_country{ $country }; 46 my $norm = ($val - $min) / 47 $max * $#SYMBOLS; 48 49 $by_country{ $country } = $norm; 50 } 51 52 my $chld = join "", keys %by_country; 53 my $data = join "", 54 map { $SYMBOLS[ $_ ] } 55 values %by_country; 56 57 # Fetch chart 58 my $ua = LWP::UserAgent->new(); 59 60 my $uri = URI::URL->new( 61 "http://chart.apis.google.com/chart"); 62 63 $uri->query_form( 64 cht => "t", 65 chs => "440x220", 66 chtm => "world", 67 chd => "s:$data", 68 # white, yellow, red 69 chco => "ffffff,f4ed28,f11414", 70 chld => $chld, 71 # light blue 72 chf => "bg,s,EAF7FE" 73 ); 74 75 my $resp = $ua->get($uri); 76 77 # Print image on success 78 if($resp->is_success()) { 79 80 open FILE, ">file.png" or die; 81 print FILE $resp->content(); 82 close FILE; 83 system ("eog file.png"); 84 } else { 85 86 die $resp->request->url() . " failed\n"; 87 }
Im Erfolgsfall zählt
Zeile 30 den Hash-Eintrag dieses Landes um Eins hoch und weiter geht
es mit der nächsten Logzeile. Da nicht alle URLs interessieren, sondern
nur die von Spammern generierten, filtert Zeile 24 alle Einträge aus,
deren Pfadangabe (Hash-Key ``file'') nicht auf den regulären Ausdruck
posting
passt. Dies ist an die lokalen Verhältnisse anzupassen und
sollte nur URLs herausfiltern, die Spammer zum Posten auf dem zu
überwachenden Diskussionsforum nutzen.
Ab Zeile 37 beginnt dann die Normalisierung der Daten und die Umwandlung
ins Google-Format. Da die Zahlenwerte für jedes Land im Hash %by_country
nicht nur im Bereich 0-61 liegen, sondern beliebige Werte annehmen können,
muss spam2geo
die Grenzen des
gesamten Wertebereichs mit min()
und max()
aus List::Utils ermitteln.
Anschließend quetscht sie die darzustellenden Zahlenwerte mittels
Subtraktion von $min
und Teilung durch $max
in den Bereich zwischen
0 und 1 und multipliziert letzteren Wert mit der Anzahl der verfügbaren
Codierungszeichen minus 1. So steht in $norm
jeweils eine
Fließkommazahl, deren Integerwert sich als Index in den Array @SYMBOLS
nutzen
lässt und so den gesamten Wertebereich auf ein Element dieses Arrays
abbildet.
Die Zeilen 52 und 53 setzen dann die ermittelten Werte in Strings ohne
trennende Leerzeichen zusammen und bereiten so deren Übergabe in die
URL-Parameter chld
(Ländercodes) und chd
(Werte) vor. Die Reihenfolge,
in der die Funktionen keys
und values
die Hashkeys bzw. Werte
zurückliefert, ist aus Programmierersicht zufällig, aber innerhalb eines
Perlscripts konstant und dem Google-Service egal.
Die Kommunikation mit dem Google-Server erledigt der LWP::UserAgent über das
HTTP-Protokoll. Die URL-Parameter setzt die Methode query_form()
, die
gleich automatisch eventuell notwendige URL-Kodierungen vornimmt.
Der Parameter cht
gibt den von Googles ``Charts'' service genutzten
Charts-Typ an und wird für eine Weltkarte
auf "t"
(wohl für 'topological') gesetzt. Es besteht die Möglichkeit,
die Ansicht auf einzelne Kontinente zu beschränken, doch für eine
gesamte Weltkarte steht der Parameter chtm
auf ``world''.
Die Ausmaße des produzierten Bildes gibt der Parameter chs
mit
440x220 Pixeln vor. Die in chco
als Hex-RGB-Werte angegebenen Farben
Weiß, Gelb und Rot legen die durchzuführende Länder-Einfärbung für
minimale, mittlere und maximale Werte fest. In der vorgegebenen Einstellung
bleiben Länder mit Werten um 0 weiß, Werte um 30 färben ein Land Gelb, und
Werte um 60 produzieren Rot. Der String ``bg,s,EAF7FE'' für den Parameter
chf
steht für ``background'' (Hintergrundfarbe), ``solid'' (ganzflächig),
und dem Hex-Wert für ein ganz helles Blau für die Ozeane der Welt.
Alles zusammen ergibt dann einen URL wie zum Beispiel
http://chart.apis.google.com/chart?cht=t&chs=440x220&chtm=world& chd=s%3ABFAABAHGQAAA8BAAAAAAAaBAA&chco=ffffff%2Cf4ed28%2Cf11414& chld=GBNLHKEELVKRRUSAPAMDCASECNDEPKITPLINMEBRCZUSUAESFR& chf=bg%2Cs%2CEAF7FE
wofür Google innerhalb von Sekundenbruchteilen einen Graphen wie den in
Abbildung 4 liefert. Kommentiert man Zeile 24 in spam2geo
hingegen aus,
bildet der Graph die Verteilung aller eingehenden URLs ab, wie in Abbildung
5 gezeigt. Während also die meisten Spam-Requests aus China und den USA
stammen, ist die Hauptkundschaft der Website im deutschen Raum zu suchen.
Der System-Befehl ``eog file.png'' zeigt die von Google
produzierte und per Webrequest eingeholte Datei sofort mit
der Utility ``eye of gnome'' an.
Abbildung 4: Spammer kommen hauptsächlich aus China und Nordamerika. |
Abbildung 5: User der Website, hauptsächlich aus Deutschland. |
Nach dem Herunterladen der MaxMind-Datenbank GeoIP.dat.gz
von [3] sollte die
dekomprimierte Datei GeoIP.dat
mitsamt dem Skript spam2geo
im aktuellen Verzeichnis landen. Die CPAN-Module IP::Country::MaxMind,
Geo::IP::PurePerl, List::Utils, ApacheLog::Parser,
und alle ihre Abhängigkeiten installieren sich am
einfachsten mit einer CPAN-Shell. Zur Nutzung der Google-API ist keinerlei
Registrierung erforderlich. Zeile 24 in spam2geo
muss noch auf die
lokalen Verhältnisse angepasst werden, in dem der Pattern-Match /posting/
so verändert wird, dass er nur auf URLs passt, die Spammer nutzen, um
Diskussionsforen mit ihren parasitären Eintragen zuzupflastern.
Für tiefergehende Analysen wie zum Beispiel die Anzahl der Forum-Requests im Vergleich zu anderen Aktivitäten oder die bevorzugten (unter Umständen simulierten) Browsertypen der Spammer, sei auf die reiche Auswahl der Google-Charts-API auf [4] hingewiesen, die derlei Information in ähnlich eleganten Charts grafisch aufbereitet.
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2009/01/Perl
Snickers-Werbung, ein genervter Mann im Schlafanzug stöbert einen Telemarketer auf: http://www.youtube.com/watch?v=R6QATC2C0h8
Die freie MaxMind-GeoIP-Datenbank zum Herunterladen: http://www.maxmind.com/download/geoip/database/
``Maps''-Charts des Google Charts Webservice: http://code.google.com/apis/chart/types.html#maps
Michael Schilliarbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com. |