Donnerstag, 18. Januar 2018

Matomo Server Log Tracking vs. Javascript Tracking

Mit dem Trackingtool Matomo (Piwik) können auf drei verschiedene Art und Weisen Statistiken über die Besuche und die Nutzung einer Webseite erstellt werden. Die Statistiken können mittels einem Javascript, einem Image oder per Server Log Dateien erstellt werden. In den drei verschiedenen Art und Weisen gibt es Unterschiede in den getrackten Statistikdaten.

In diesem Artikel werden Möglichkeiten erläutert, mit denen die Unterschiede zwischen dem Tracking mit Javascript und dem Tracking mittels den Server access_log Dateien aufgespürt werden können.

Das Javascript Tracking ist abhängig davon, ob im Browser der Webseitenbesucher Javascript aktiviert ist. Zudem ist es abhängig von einem eventuellen Do Not Track, dass im Browser aktiviert ist und von Matomo (Piwik) respektiert wird, wenn dies so eingestellt ist.

Das Image Tracking kommt zum Einsatz, wenn kein Javascript im Browser aktiviert ist.

Beide Methoden können von AdBlock Plugins und AddOns erkannt und geblockt werden.

Das Tracking mittels den Server access_log Dateien ist weitestgehend unabhängig von eventuellen Browser-Einstellungen. Die Statistiken sind somit ungefiltert.
In der Server access_log Datei wird jeder Server Request, also jede Anfrage an den Server, gelogt. Die geloggten Informationen sind die IP-Adresse, das Datum mit Uhrzeit und der Full User Agent String, mit Informationen über das vom Besucher verwendete Betriebssystem und den verwendeten Browser mit Name und Version. Desweiteren die HTTP Referer URL und die URL der Anfrage.

Die von Matomo getrackten Statistiken mittels Javascript Tracking und Image Tracking können nicht anders als von Matomo selbst ausgewertet werden. Also es gibt keine weiteren Daten, die Matomo nicht selbst schon verarbeitet und auswertet.

Die von Matomo getrackten Statistiken mittels den Server access_log Dateien können auch mit anderen Methoden und Tools ausgewertet werden, da hier eine Roh-Datenquelle vorliegt, eben die Server access_log Dateien. Das ist unter anderem mit Kommandozeilenkommandos möglich.

Die in den Server access_log Dateien gespeicherten Daten und Informationen können mit hilfreichen command-line commands gezählt und sortiert werden. Hier nun ein paar hilfreiche Kommandos für die Kommandozeile (bash, shell).


SERVER REQUESTS
Linux grep: zähle die Server-Anfrage-Einträge für einen Tag, ohne Filter.
Linux grep: count the server request entries for one day, without filterwords.
Bitte den Suchstring (Datum) und den Pfad anpassen.
Please adjust the search string (date) and the path.
grep -o -i '18/Jan/2018' /path/to/logs/access_log | wc -l
Result: In der Kommandozeile wird eine Zahl als Ergebnis ausgegeben.


SERVER REQUESTS + Filter
Linux grep: zähle die Server-Anfrage-Einträge für einen Tag, mit Filter (bekannte Suchmaschinen).
Linux grep: count the server request entries for one day, with filterwords (searchrobots).
grep -v -i "piwik\|bot\|crawl\|spider\|slurp\|google\|bing\|yandex" /path/to/logs/access_log | grep -o -i '18/Jan/2018' | wc -l
Result: In der Kommandozeile wird eine Zahl als Ergebnis ausgegeben.


IP ADDRESSES
Linux grep: zähle die verschiedenen IP Adressen für einen Tag, ohne Filter.
Linux grep: count the unique IP addresses for one day, without filterwords.
Bitte den Suchstring (Datum) und den Pfad anpassen.
Please adjust the search string (date) and the path.
grep -w '18/Jan/2018' /path/to/logs/access_log | awk '{print "# "$1}' | sort | uniq -c | sort -n | tee >(wc -l) >unique-ips.txt
Result: In der Kommandozeile wird eine Zahl als Ergebnis ausgegeben. Die Zahl kann ungefähr mit den Visitors übereinstimmen. Zudem werden die IPs in eine Datei geschrieben.

IP ADDRESSES + Filter
Linux grep: zähle die verschiedenen IP Adressen für einen Tag, mit Filter.
Linux grep: count the unique IP addresses for one day, with filterwords.
Bitte den Suchstring (Datum) und den Pfad anpassen.
Please adjust the search string (date) and the path.
grep -v -i "piwik\|bot\|crawl\|spider\|slurp\|google\|bing\|yandex" /path/to/logs/access_log | grep -w '18/Jan/2018' | awk '{print "# "$1}' | sort | uniq -c | sort -n | tee >(wc -l) >unique-ips-filter.txt
Result: In der Kommandozeile wird eine Zahl als Ergebnis ausgegeben. Die Zahl kann ungefähr mit den Visitors übereinstimmen. Zudem werden die IPs in eine Datei geschrieben.


Im Folgenden weitere Kommandozeilenbefehle für eine bessere Übersicht der Server Log access_log Datei Einträge.

Für eine eventuelle Nutzung in OpenOfficeCalc wird eine "#" zur Spaltentrennung mitgeschrieben.
For eventual use in OpenOfficeCalc, a "#" has to be added to separate the columns.
Bitte den Suchstring (Datum) und die Pfade anpassen.
Der Filter ist beliebig erweiterbar mit "\|filterwort".
Die Ergebnisse der folgenden Befehle werden in verschiedene Dateien im aktuellen Pfad geschrieben.
Aktuellen Pfad anzeigen über command-line command: pwd


COUNT USER AGENTS (+ Print)
Checke für einen bestimmten Tag, welche User Agents am häufigsten benutzt wurden, ohne Filter.
Sortiert nach User Agents.
Check for a specific day, which user agents were used most frequently, without filter.
Sorted by user agents.
grep -w '18/Jan/2018' /path/to/logs/access_log | awk -F\" '{print "# "$6}' | sort | uniq -c | sort -n | tee >(wc -l) >sort-useragents.txt

COUNT USER AGENTS + Filter (+ Print)
Checke für einen bestimmten Tag, welche User Agents am häufigsten benutzt wurden, mit Filter.
Sortiert nach User Agents.
Check for a specific day, which user agents were used most frequently, with filter.
Sorted by user agents.
grep -v -i "piwik\|bot\|crawl\|spider\|slurp\|google\|bing\|yandex" /path/to/logs/access_log | grep -w '18/Jan/2018' | awk -F\" '{print "# "$6}' | sort | uniq -c | sort -n | tee >(wc -l) >sort-useragents-filter.txt
Die Anzahl der verschiedenen User Agents gibt keinen Aufschluß über Visitors und Pageviews. Das Ergebnis kann nach Brut Force Attacks gecheckt werden.


COUNT FILE REQUESTS (+ Print)
Checke für einen bestimmten Tag, welche Files and Folders wie oft aufgerufen wurden, ohne Filter.
Sortiert nach aufgerufenen Files/Folders.
Check for a specific day, which files and folders were requested as often, without filter.
Sorted by requested File/Folder and User Agent.
grep -w '18/Jan/2018' /path/to/logs/access_log | sed 's/GET//' | sed 's/HTTP\/1\.1//' | awk -F\" '{print "#"$2}' | sort | uniq -c | sort -n | tee >(wc -l) >sort-request-files.txt

COUNT FILE REQUESTS + Filter (+ Print)
Checke für einen bestimmten Tag, welche Files and Folders wie oft aufgerufen wurden, mit Filter.
Sortiert nach aufgerufenen Files/Folders.
Check for a specific day, which files and folders were requested as often, with filter.
Sorted by requested File/Folder and User Agent.
grep -v -i "piwik\|bot\|crawl\|spider\|slurp\|google\|bing\|yandex" /path/to/logs/access_log | grep -w '18/Jan/2018' | sed 's/GET//' | sed 's/HTTP\/1\.1//' | awk -F\" '{print "#"$2}' | sort | uniq -c | sort -n | tee >(wc -l) >sort-request-files-filter.txt
Die Anzahl der File Requests gibt keinen Aufschluß über die Pageviews. Jedes einzelne File in einer Webpage benötigt einen Server Request, der in der access_log gelogt wird. Für eine Anzahl der Pageviews wäre ein umfangreicher Filter notwendig.


COUNT FILE REQUESTS / USER AGENTS (+ Print)
Checke für einen bestimmten Tag, welche Files and Folders wie oft mit dem selben User Agent aufgerufen wurden, ohne Filter.
Sortiert nach aufgerufenen Files/Folders und User Agents.
Check for a specific day, which files and folders are requested as often with the same user agent, without filter.
Sorted by requested File/Folder and User Agent.
grep -w '18/Jan/2018' /path/to/logs/access_log | sed 's/GET//' | sed 's/HTTP\/1\.1//' | awk -F\" '{print "#"$2"# "$6}' | sort | uniq -c | sort -n | tee >(wc -l) >sort-request-files-useragents.txt

COUNT FILE REQUESTS / USER AGENTS + Filter (+ Print)
Checke für einen bestimmten Tag, welche Files and Folders wie oft mit dem selben User Agent aufgerufen wurden, mit Filter.
Sortiert nach aufgerufenen Files/Folders und User Agents.
Check for a specific day, which files and folders are requested as often with the same user agent, with filter.
Sorted by requested File/Folder and User Agent.
grep -v -i "piwik\|bot\|crawl\|spider\|slurp\|google\|bing\|yandex" /path/to/logs/access_log | grep -w '18/Jan/2018' | sed 's/GET//' | sed 's/HTTP\/1\.1//' | awk -F\" '{print "#"$2"# "$6}' | sort | uniq -c | sort -n | tee >(wc -l) >sort-request-files-useragents-filter.txt
Die Ausgabe der File Requests und User Agents gibt Aufschluß, welche Files und Folders von welchen User Agents requested wurden. Hiermit können Brut Force Attack User Agents erkannt werden.


Matomo (Piwik) filtert Bots, Crawler, Spider und dergleichen mittels einer Blacklist im von Matomo verwendeten DeviceDetector, die in dieser Datei zu finden ist:
/piwik/vendor/piwik/device-detector/regexes/bots.yml


CHECK SERVER LOG ENTRIES OVER IP

Im folgenden ein PHP Script, mit dem die Server Log Einträge einer IP, die im Matomo Visitor Log angezeigt wird, angezeigt werden können. Das Ergebnis gibt Auskunft darüber, was im Server access_log File gespeichert ist und was Matomo Javascript Tracking in der Datenbank gespeichert hat.

Bitte die Daten für die Datenbank-Verbindung eintragen, den Pfad zur access_log Datei und eine IP aus der Matomo Visitor Log Anzeige. Die gewählte IP muss im aktuellen Zeitraum des access_log Files und der Datenbank Einträge sein. Beide werden regelmäßig archiviert. Das Script greift nicht auf archivierte Daten zurück. Ein Server access_log File wird einmal pro Woche, oder alle 14 Tage archiviert und die Matomo Datenbank default einmal im Monat. Also ein Check kurz nach einer Archivierung ist ein ungünstiger Zeitpunkt.

Das PHP Script ist geschrieben für gekürzte IPs aus den Matomo Visitor Log Anzeigen.

Server Log IPv4: 123.456.789.0
Matomo IPv4: 123.456.789.0

Server Log IPv6: 2018:0000:0000::
Matomo IPv6: 2018:0000:0000:0000::

Falls die IPs bei dir anders gespeichert werden, dann bitte den PHP Code diesbezüglich anpassen.

<?php
error_reporting(E_ALL);
ini_set("display_errors", 1);

// DATABASE CONNECTION Username Password Database
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "databasename";

// PATH TO YOUR access_log FILE
$accesslogfile = file('/path/to/your/access_log');

// SEARCH FOR VISITOR IP
$ipvisitor = "2018:0000:0000:0000::";
echo "<br>Search for IP: " . $ipvisitor . "<br>";
/** PIWIK LICENSE START - MATOMO function sanitizeIp
* Piwik - free/libre analytics platform @link http://piwik.org @license http://www.gnu.org/licenses/lgpl-3.0.html LGPL v3 or later
* Removes the port and the last portion of a CIDR IP address.
* @param string $ipString The IP address to sanitize. @return string */
function sanitizeIp($ipString) {
$ipString = trim($ipString);
$posSlash = strrpos($ipString, '/');
if ($posSlash !== false) {
$ipString = substr($ipString, 0, $posSlash); }
$posColon = strrpos($ipString, ':');
$posDot = strrpos($ipString, '.');
if ($posColon !== false) {
$posRBrac = strrpos($ipString, ']');
if ($posRBrac !== false && $ipString[0] == '[') {
$ipString = substr($ipString, 1, $posRBrac - 1); }
if ($posDot !== false) {
if ($posColon > $posDot) {
$ipString = substr($ipString, 0, $posColon); }
} else if (strpos($ipString, ':') === $posColon) {
$ipString = substr($ipString, 0, $posColon); }
return $ipString; }}
// PIWIK LICENSE END

if (strpos($ipvisitor, ":")) {
$ipvisitoripv6 = explode(":", $ipvisitor);
$ipvisitoraccesslog = $ipvisitoripv6[0] . ":" . $ipvisitoripv6[1] . ":" . $ipvisitoripv6[2] . "::";
} else {
$ipvisitoraccesslog = $ipvisitor;
}
echo "<br>";
$accesslogfilecount = count($accesslogfile);
echo "ACCESS_LOG SERVER ENTRIES";
echo "<br>";
$accesslogfileiparray = [];
$accesslogfileuseragentarray = [];
for ($i = 0; $i < $accesslogfilecount; $i++ ) {
$accesslogfileexplodespace = explode(" ", $accesslogfile[$i]);
$accesslogfileip = $accesslogfileexplodespace[0];
$accesslogfiledatestring = $accesslogfileexplodespace[3] . " " . $accesslogfileexplodespace[4];
$accesslogfiledatetime = preg_replace("/(^\[|\]$)/", "", $accesslogfiledatestring);
$accesslogfileexplodequote = explode('"', $accesslogfile[$i]);
$accesslogfilesource = preg_replace("/(^GET | HTTP\/1.1$)/", "", $accesslogfileexplodequote[1]);
$accesslogfileuseragent = $accesslogfileexplodequote[5];
if ($ipvisitoraccesslog == $accesslogfileip) {
array_push($accesslogfileiparray, $accesslogfileip);
array_push($accesslogfileuseragentarray, $accesslogfileuseragent);
echo "<br>";
echo $accesslogfiledatetime;
echo " - ";
echo substr($accesslogfilesource, 0, 100);
echo "<br>";
}}
echo "<br>";
echo implode(", ", array_unique($accesslogfileiparray));
echo "<br>";
echo implode("<br>", array_unique($accesslogfileuseragentarray));
echo "<br>";
echo "<br>";
echo "------------------------------------------------------------";
echo "<br>";

// DATABASE CONNECTION
$databaseconnect = mysqli_connect($servername, $username, $password, $dbname);
if (!$databaseconnect) {
die("Connection failed: " . mysqli_connect_error());
}
/////////////////////////////////////////////////////////////////
// LOG_VISIT SELECT
$log_visit_select = "SELECT idvisit, idvisitor, visit_first_action_time, visit_last_action_time, location_ip FROM piwik_log_visit";
$log_visit_result = mysqli_query($databaseconnect, $log_visit_select);
// LOG_VISIT WHILE
echo "<br>LOG_VISIT DATABASE ENTRIES<br>";
if (mysqli_num_rows($log_visit_result) > 0) {
while ($log_visit_row = mysqli_fetch_assoc($log_visit_result)) {
if (strpos(inet_ntop($log_visit_row["location_ip"]), ":")) {
$locationipv6 = explode(":", inet_ntop($log_visit_row["location_ip"]));
$logvisitlocationip = $locationipv6[0] . ":" . $locationipv6[1] . ":" . $locationipv6[2];
$accesslogfileip = substr($accesslogfileip, 0, -2);
} else {
$logvisitlocationip = inet_ntop($log_visit_row["location_ip"]);
}
if (inet_ntop($log_visit_row["location_ip"]) == $ipvisitor) {
echo inet_ntop($log_visit_row["location_ip"]) . " - " . bin2hex($log_visit_row["idvisitor"]) . "<br>";
$ifipdatabasefound = 1;
$visitoridbin = $log_visit_row["idvisitor"];
}}}
if (!isset($ifipdatabasefound)) {
echo "no database entries";
}
/////////////////////////////////////////////////////////////////
// LOG_LINK_VISIT_ACTION SELECT
if (isset($visitoridbin)) {
$log_link_visit_action_select = "SELECT idvisitor, server_time, idaction_url FROM piwik_log_link_visit_action WHERE idvisitor='$visitoridbin'";
$log_link_visit_action_result = mysqli_query($databaseconnect, $log_link_visit_action_select);
}
$idaction_url = [];
if (mysqli_num_rows($log_link_visit_action_result) > 0) {
while ($log_link_visit_action_row = mysqli_fetch_assoc($log_link_visit_action_result)) {
if ($log_link_visit_action_row["idvisitor"] == $visitoridbin) {
$log_link_visit_action_row_idaction_url = $log_link_visit_action_row["idaction_url"];
array_push($idaction_url, $log_link_visit_action_row_idaction_url);
}}}
/////////////////////////////////////////////////////////////////
// LOG_ACTION SELECT
$log_action_select = "SELECT idaction, name, type FROM piwik_log_action WHERE type='1' AND idaction IN (".implode(',',array_filter(array_unique($idaction_url))).")";
$log_action_result = mysqli_query($databaseconnect, $log_action_select);
// LOG_ACTION WHILE
echo "<br>LOG_ACTION DATABASE ENTRIES<br>";
if ($log_action_result != false) {
echo mysqli_num_rows($log_action_result) . " entries found";
} else {
echo "0 entries found";
}
echo "<br>";
if ($log_action_result != false) {
while ($log_action_row = mysqli_fetch_assoc($log_action_result)) {
echo $log_action_row["name"] . "<br>";
}}
echo "<br>";
echo "<br>";
mysqli_close($databaseconnect);
?> 

FAZIT: Für einen Vergleich zwischen Server Log Tracking und Javascript Tracking ist es am besten, wenn eine zweite Instanz von Matomo installiert wird, mit eigener Datenbank, mittels der das Vergleichs-Tracking erfasst wird. Das klingt erstmal nach viel Aufwand, aber ein Vergleichs-Script schreiben ist viel mehr Aufwand, da damit Matomo nachgebaut werden müßte.


Thema SPAM-BOTS und BRUT-FORCE-ATTACKS

Spam-Bots und Brut-Force-Attacken werden mit Fake-User Agents betrieben.

Der Sinn von Spam-Bots ist, dass diese Domain-URLs als HTTP-Referer in den Statistiken hinterlegen und ahnungslose Administratoren aus Neugier auf diese URLs klicken.

Ein sehr bekannter Brut-Force-Attack User Agent ist der Firefox 40.1. Es gab nie eine Version Firefox 40.1.

"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"

Solcherlei Fake User Agents kann der Zugang zum Server mittels eines Eintrags in der .htaccess verboten werden.
<IfModule mod_setenvif.c>
SetEnvIfNoCase User-Agent "Firefox/40.1" bad_bot
Order Allow,Deny
Allow from All
Deny from env=bad_bot
</IfModule>
Die Funktion kann mittels eines "User Agent Switcher" Browser AddOn, bei dem eigene User Agent Namen hinzugefügt werden können, getestet werden.

Zu erkennen sind User Agents von Brut-Force-Attacken meits daran, dass sie zuerst das Base-Verzeichnis "/" aufrufen und danach die "wp-login.php" Datei, oder umgekehrt. Wordpress Usern wird empfohlen den Zugriff auf diese Datei mittels .htpasswd zu schützen. Hier ein Artikel dazu: htaccess - Sicheres Passwort - Passwort Schutz.


Keine Garantie. Keine Gewährleistung. Kein Support.
Verwendung auf eigenes Risiko.