Aus Linux-Magazin 02/2009

Perl-Skript zeichnet Spam-Karten nach Webserver-Logs

© Windrose, Pixelio.de

Eine Kombination aus IP-Adressen-Datenbank und Googles Chart-Service verrät, aus welchen Regionen die Link-Spammer einer Webseite kommen.

Ich fände es äußerst befriedigend, könnte ich das Büro eines Spammers oder Telemarketingers aufspüren, hinfahren und den PC des Fernwerbers in Kleinholz verwandeln. Eigentlich genau so, wie in dem amerikanischen Werbespot der Firma Snickers [2]. Leider ist das aus legalen und logistischen Gründen oft nicht möglich. Im wahren Leben erledigen außerdem nicht die Gauner selbst die Drecksarbeit, sondern Botnetze infizierter PCs. Trotzdem wäre es interessant, einmal grafisch darzustellen, aus welchen Regionen der Welt die meisten Spammer-Aktivitäten so kommen.

Fährte aufnehmen

Das Internet ist die ideale Plattform für anonyme Gaunereien, aber eine Spur hinterlassen die Verbrecher dennoch: Jeder eingehende Request führt die IP-Adresse des Senders mit sich (Abbildung 1). Die lässt sich zwar auch manipulieren, aber das ist nicht ganz so einfach und den meisten Spammern zu umständlich.

Abbildung 1: Link-Spammer hinterlassen im Access-Log des Webservers eine IP-Adresse.

Abbildung 1: Link-Spammer hinterlassen im Access-Log des Webservers eine IP-Adresse.

Das DNS, das Hostnamen ihre IP-Adresse zuweist, versteht sich auch auf die umgekehrte Zuordnung. Falls der Provider alles ordnungsgemäß aufgesetzt hat, nimmt ein so genannter DNS-Reverse-Lookup eine IP-Adresse entgegen und gibt – wie beispielsweise das Skript »revlookup« (Listing 1) in Abbildung 2 – einen qualifizierten Hostnamen aus, der auf den Provider schließen lässt.

Listing 1:
»revlookup«

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 }

Abbildung 2 zeigt, dass die beim Spammen erwischte IP 69.162.110.146 dem Provider Lstn.net gehört. Eine freundliche E-Mail an dessen Webmaster mit genauer Angabe der Adresse, der Uhrzeit (wichtig, da diese Adressen unter Umständen dynamisch vergeben werden) bringt mit etwas Glück den Spammer zum Schweigen.

Abbildung 2: Ein Reverse-DNS-Lookup bringt oft die einer IP-Adresse zugeordnete Domain zum Vorschein.

Abbildung 2: Ein Reverse-DNS-Lookup bringt oft die einer IP-Adresse zugeordnete Domain zum Vorschein.

Wer bist du?

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 Aufruf der Perl-Funktion »gethostbyaddr()« zurück. Letztere führt den DNS-Reverse-Lookup aus und liefert im Erfolgsfall einen String mit dem Hostnamen, im Fehlerfall »undef«. Der Vorgang kann allerdings einige Sekunden dauern, je nachdem, wie beschäftigt der genutzte DNS-Server gerade ist und wie viele Kollegen er zur Beantwortung der Frage konsultiert.

Das Kommandozeilen-Utility »whois« verkraftet auch IP-Adressen als Argumente. Abbildung 3 zeigt, dass der Provider Limestone Networks gleich eine E-Mail-Adresse für Beschwerden angibt, an die sich Spam-Opfer wenden können. Das Ganze geht auch in Perl, etwa mit dem CPAN-Modul Net::Whois::Raw, doch erfolgt der Lookup dabei über die Server der Firma Network Solutions, die nach etwa 100 Lookups in kurzer Folge den Zugang sperrt.

Abbildung 3: Der Whois-Eintrag für die ertappte IP-Adresse zeigt die Daten des Internetproviders des Spammers.

Abbildung 3: Der Whois-Eintrag für die ertappte IP-Adresse zeigt die Daten des Internetproviders des Spammers.

Viele Spammer arbeiten jedoch mit IP-Adressen, die keinen Reverse-Eintrag im DNS-System aufweisen. Aber auch dann ist eine Lokalisierung in gewissen Grenzen möglich, denn Service-Provider bekommen die Adressen in Blöcken zugewiesen und es existieren Datenbanken, die die geografische Lage der Provider verraten, denen eine bestimmte Adresse zugeteilt wurde.

Die Firma Maxmind bietet unter [3] eine Datenbankdatei an, die sich für nicht-kommerzielle Zwecke kostenlos nutzen lässt. Die genauen Lizenzbedingungen liegen im selben Ordner wie die Datenbank. Das CPAN-Modul IP::Country::MaxMind stellt ein passendes API zur Verfügung, damit niemand mit den Binärdaten direkt herumfuhrwerken muss. Die in der Datenbank gespeicherten IP-Zuweisungen ändern sich nur sehr langsam, sodass Updates nur alle paar Monate nötig sind.

Nach der Installation des Moduls, das zusätzlich noch 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 zurück, zum Beispiel »DE« für Deutschland.

Karten zeichnen

Für eine bequeme grafische Darstellung dieser Codes auf einer Weltkarte bietet sich etwa das Google-Charts-API [4] an. Spielt man dem Google-Server die Wertepaare verpackt in einer URL zu, dann antwortet dieser mit einer Bilddatei im PNG-Format. Das Datenformat der Wertepaare ist etwas gewöhnungsbedürftig, denn auch mittelgroße Datenmengen müssen noch in das stark eingeschränkte Platzangebot einer URL mit Query-Parametern passen.

Das einfachste Datenformat des Google-API, das so genannte Simple Encoding, lässt über das API nur Werte von 0 bis 61 zu, kodiert mit »A-Z« (0 bis 25), »a-z« (26 bis 51) beziehungsweise »0-9« (52 bis 61). Möchte der Programmierer beispielsweise Deutschland den Häufigkeitswert 23 zuweisen, den USA den Wert 3 und Japan den Wert 60, dann kodiert er zum einen die Ländercodes in dem URL-Parameter »chld« mit »DEUSJP« (zwei Buchstaben pro Land, ohne Leerzeichen angeben) und zum anderen die Häufigkeiten in »chd« mit »s:XD8« (»s« für Simple Encoding, »X« entspricht 23, »D« steht für 3 und »8« für 60).

Das Skript »spam2geo« in Listing 2 fasst alles bisher Gezeigte zusammen. Es analysiert die Datei »access.log« eines durch 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 Spam-Versenders, ein Aufruf der Methode »inet_atocc()« in Zeile 27 gibt den zweibuchstabigen Ländercode zurück – falls die Datenbank diesen Adressenbereich findet.

Listing 2:
»spam2geo«

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 "", values %by_country;
54
55   # Fetch chart
56 my $ua = LWP::UserAgent->new();
57
58 my $uri = URI::URL->new(
59      "http://chart.apis.google.com/chart");
60
61 $uri->query_form(
62   cht  => "t",
63   chs  => "440x220",
64   chtm => "world",
65   chd  => "s:$data",
66     # white, yellow, red
67   chco => "ffffff,f4ed28,f11414",
68   chld => $chld,
69     # light blue
70   chf  => "bg,s,EAF7FE"
71 );
72
73 my $resp = $ua->get($uri);
74
75   # Print image on success
76 if($resp->is_success()) {
77
78   open FILE, ">file.png" or die;
79   print FILE $resp->content();
80   close FILE;
81   system ("eog file.png");
82 } else {
83
84   die $resp->request->url() . " failedn";
85 }

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 benutzten, 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 nutzen.

Normiert kodiert

Ab Zeile 37 beginnen 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 bis 61 liegen, sondern beliebige Werte annehmen können, muss »spam2geo« die Grenzen des gesamten Wertebereichs mit »min()« und »max()« aus List::Utils ermitteln.

Dann quetscht sie die darzustellenden Zahlenwerte mittels Subtraktion von »$min« und Teilung durch »$max« in den Bereich zwischen 0 und 1 und multipliziert diesen Wert mit der Anzahl der verfügbaren Kodierungszeichen minus 1. So steht in »$norm« jeweils eine Fließkommazahl, deren Integerwert sich als Index im Array »@SYMBOLS« nutzen lässt und so den gesamten Wertebereich auf ein Element dieses Array abbildet.

Die Zeilen 52 und 53 setzen dann die ermittelten Werte in Strings ohne trennende Leerzeichen zusammen und bereiten damit deren Übergabe in die URL-Parameter »chld« (Ländercodes) und »chd« (Werte) vor. Die Reihenfolge, in der die Funktionen »keys« und »values« die Hashkeys beziehungsweise Werte zurückliefern, ist ganz zufällig, aber innerhalb eines Perl-Skripts 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 Chart-Service genutzten Chart-Typ an und wird für eine Weltkarte auf »t« (wohl für topological) gesetzt. Es besteht auch die Möglichkeit, die Ansicht auf einzelne Kontinente zu beschränken, für eine Weltkarte muss der Parameter »chtm« auf »world« eingestellt sein. Die Ausmaße des Bildes gibt der Parameter »chs« mit 440 mal 220 Pixeln vor. Die in »chco« als Hex-RGB-Werte angegebenen Farben Weiß, Gelb und Rot legen die Länder-Einfärbung für minimale, mittlere und maximale Werte fest. Der String »bg,s,EAF7FE« für den Parameter »chf« steht für »Background« (Hintergrund), »solid« (ganzflächig) und für den Hexwert für das ganz helle Blau der Ozeane. Alles zusammen ergibt dann eine 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

Google liefert dafür innerhalb von Sekundenbruchteilen einen Graphen wie in Abbildung 4 zurück. Wer die Zeile 24 in »spam2geo« auskommentiert, bekommt stattdessen einen Graphen, der die Verteilung aller eingehenden URLs abbildet, wie Abbildung 5 zeigt. Die meisten Spam-Requests kommen aus China und den USA, die Hauptkundschaft der Website dagegen aus Deutschland.

Abbildung 4: Die Spammer kommen hauptsächlich aus China und Nordamerika. Die entsprechenden Weltgegenden sind rot eingefärbt.

Abbildung 4: Die Spammer kommen hauptsächlich aus China und Nordamerika. Die entsprechenden Weltgegenden sind rot eingefärbt.

Abbildung 5: Die User der Website und Opfer des Spams stammen dagegen hauptsächlich aus Deutschland.

Abbildung 5: Die User der Website und Opfer des Spams stammen dagegen hauptsächlich aus Deutschland.

Installation

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 des Google-API ist keinerlei Registrierung erforderlich. Zeile 24 in »spam2geo« ist schließlich noch auf die lokalen Verhältnisse anzupassen. Dazu muss der Anwender das verwendete Suchmuster »/posting/« jeweils so verändern, dass es nur auf solche URLs passt, die Spammer nutzen, um Diskussionsforen mit ihren lästigen Einträgen zuzupflastern.

Für tiefer gehende Analysen, 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 des Google-Charts-API auf [4] hingewiesen, die derlei Information in eleganten Charts grafisch aufbereitet. (jcb)

Infos

[1] Listings zu diesem Artikel: [ftp://www.linux-magazin.de/pub/listings/magazin/2009/01/Perl]

[2] Snickers-Werbung: [http://www.youtube.com/watch?v=R6QATC2C0h8]

[3] Freie Maxmind-GeoIP-Datenbank zum Herunterladen: [http://www.maxmind.com/download/geoip/database]

[4] Maps-Charts des Google Charts Webservice: [http://code.google.com/apis/chart/types.html#maps]

Der Autor


Michael Schilli arbeitet 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.

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben