Fichiers journaux
About 25% of the activity is 200-OKEnviron 25 % de l'activité est 200-OK
J'ai découvert que mon serveur Web conservait certains fichiers journaux depuis un an et d'autres depuis un mois. J'ai exploré trois types de fichiers journaux contenant des informations sur les visiteurs du serveur (il existe d'autres fichiers qui semblent moins intéressants). Voici quelques exemples typiques :
Le journal d'authentification (les adresses IP ont changé)
Oct 23 06:02:24 server1 sshd[20172]: Failed password for invalid user admin from 192.168.1.1 port 64721 ssh2
Oct 23 06:09:01 server1 CRON[20181]: pam_unix(cron:session): session opened for user root by (uid=0)
Oct 23 06:09:01 server1 CRON[20181]: pam_unix(cron:session): session closed for user root
Oct 23 06:10:35 server1 sshd[20195]: Did not receive identification string from 192.168.1.1
Oct 23 06:10:40 server1 sshd[20196]: Invalid user admin from 192.168.1.1
Oct 23 06:10:40 server1 sshd[20196]: input_userauth_request: invalid user admin [preauth]
Oct 23 06:10:41 server1 sshd[20196]: pam_unix(sshd:auth): check pass; user unknown
Les tentatives d'autorisation sur un serveur Web représentent des tentatives de connexion, soit par moi, soit par un logiciel appartenant à mon serveur (CRON, ci-dessus), soit par une autre personne ou un autre ordinateur (quelqu'un essayant un identifiant utilisateur d'administrateur, ci-dessus). L'administrateur appartient à la catégorie « méchants ».
Le accès.log (Serveur Apache) (IP modifiées)
192.168.2.2 - - [30/Aug/2016:08:30:29 -0400] "GET / HTTP/1.1" 200 1714 "-" "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"
192.168.1.1 - - [30/Aug/2016:08:51:52 -0400] "GET /hndUnblock.cgi HTTP/1.0" 404 496 "-" "Wget(linux)"
192.168.1.1 - - [30/Aug/2016:08:51:52 -0400] "GET /tmUnblock.cgi HTTP/1.0" 404 495 "-" "Wget(linux)"
192.168.1.1 - - [30/Aug/2016:08:51:52 -0400] "GET /cgi/common.cgi HTTP/1.0" 404 496 "-" "Wget(linux)"
192.168.1.1 - - [30/Aug/2016:08:51:53 -0400] "GET /stssys.htm HTTP/1.0" 404 492 "-" "Wget(linux)"
192.168.1.1 - - [30/Aug/2016:08:51:53 -0400] "GET / HTTP/1.0" 200 1714 "-" "Wget(linux)"
192.168.1.1 - - [30/Aug/2016:08:51:54 -0400] "POST /command.php HTTP/1.0" 404 493 "-" "Wget(linux)"
192.168.3.3 - - [30/Aug/2016:09:09:01 -0400] "GET /?feed=rss2 HTTP/1.1" 200 794 "-" "Feedly/1.0 (+http://www.feedly.com/fetcher.html; like FeedFetcher-Google)"
Les entrées du journal d'accès représentent les tentatives d'obtention d'une page Web du serveur. La première ligne ci-dessus représente une requête valide d'un robot d'exploration Web ( Baidu , un moteur de recherche chinois). La requête get concernait / qui est la page principale. Le 200 signifie que la page a été servie correctement. Les six lignes suivantes ont toutes un résultat 404 (introuvable). Il s'agit d'un pirate informatique essayant de trouver un serveur Web mal configuré. Après plusieurs tentatives, un logiciel de surveillance (Fail2Ban) a remarqué ce qui se passait et l'a verrouillé. Avant, je pensais que Fail2Ban était vraiment important (c'est toujours le cas !), mais j'ai ensuite découvert l'UFW, le pare-feu simple et une menace encore plus grande.
Ici se trouve le UFW.log :
Oct 9 04:35:21 server1 kernel: [5110701.311961] [UFW BLOCK] IN=venet0 OUT= MAC= src=175.9.9.9 DST=192.168.1.1 LEN=60 TOS=0x00 PREC=0x00 TTL=40 ID=53848 DF PROTO=TCP SPT=10651 DPT=23 WINDOW=5808 RES=0x00 SYN URGP=0
Oct 9 04:35:24 server1 kernel: [5110704.311558] [UFW BLOCK] IN=venet0 OUT= MAC= src=175.8.8.8 DST=192.168.1.1 LEN=60 TOS=0x00 PREC=0x00 TTL=40 ID=53849 DF PROTO=TCP SPT=10651 DPT=23 WINDOW=5808 RES=0x00 SYN URGP=0
Oct 9 04:35:36 server1 kernel: [5110715.912365] [UFW BLOCK] IN=venet0 OUT= MAC= src=112.7.7.7 DST=192.168.1.1 LEN=40 TOS=0x00 PREC=0x00 TTL=39 ID=32234 PROTO=TCP SPT=60061 DPT=23 WINDOW=16244 RES=0x00 SYN URGP=0
Oct 9 04:35:46 server1 kernel: [5110726.213736] [UFW BLOCK] IN=venet0 OUT= MAC= src=67.6.6.6 DST=192.168.1.1 LEN=44 TOS=0x00 PREC=0x40 TTL=239 ID=7084 PROTO=TCP SPT=57510 DPT=23 WINDOW=14600 RES=0x00 SYN URGP=0
Oct 9 04:37:02 server1 kernel: [5110802.146865] [UFW BLOCK] IN=venet0 OUT= MAC= src=139.5.5.5 DST=192.168.1.1 LEN=40 TOS=0x00 PREC=0x00 TTL=39 ID=33753 PROTO=TCP SPT=6885 DPT=23 WINDOW=9693 RES=0x00 SYN URGP=0
Oct 9 04:37:53 server1 kernel: [5110853.056320] [UFW BLOCK] IN=venet0 OUT= MAC= src=87.4.4.4 DST=192.168.1.1 LEN=60 TOS=0x00 PREC=0x00 TTL=54 ID=61204 DF PROTO=TCP SPT=47013 DPT=23 WINDOW=5808 RES=0x00 SYN URGP=0
Oct 9 04:37:56 server1 kernel: [5110856.056659] [UFW BLOCK] IN=venet0 OUT= MAC= src=87.3.3.3 DST=192.168.1.1 LEN=60 TOS=0x00 PREC=0x00 TTL=54 ID=61205 DF PROTO=TCP SPT=47013 DPT=23 WINDOW=5808 RES=0x00 SYN URGP=0
Oct 9 04:38:02 server1 kernel: [5110862.056195] [UFW BLOCK] IN=venet0 OUT= MAC= src=87.2.2.2 DST=192.168.1.1 LEN=60 TOS=0x00 PREC=0x00 TTL=54 ID=61206 DF PROTO=TCP SPT=47013 DPT=23 WINDOW=5808 RES=0x00 SYN URGP=0
Oct 9 04:39:04 server1 kernel: [5110924.610656] [UFW BLOCK] IN=venet0 OUT= MAC= src=175.1.1.1 DST=192.168.1.1 LEN=40 TOS=0x00 PREC=0x00 TTL=43 ID=16953 PROTO=TCP SPT=35941 DPT=23 WINDOW=41293 RES=0x00 SYN URGP=0
Chaque ligne des lignes UFW BLOCK représente un autre type d'attaque sur un serveur : la recherche d'un port mal configuré. Toutes ces lignes testent DPT=23, ce qui est telnet , un ancien protocole. Notez que cela se produit au moins une fois par minute. Toute la journée. Tous les jours.
Charger les données dans une table de données
Chacun des fichiers est formaté différemment, les dates peuvent ne pas contenir l'année en cours, les champs peuvent avoir un nom = valeur ou simplement être positionnels. J'ai déplacé tous les fichiers du serveur Linux vers ma machine Windows et utilisé FilesInDirectory pour obtenir une liste de noms de fichiers en JSL. Ensuite, j'ai écrit un ensemble de modèles pour analyser efficacement les trois types de fichiers. Les colonnes du tableau de données ont été choisies pour représenter les données que je souhaitais capturer à partir de chacun des fichiers :
Columns in tableColonnes du tableau
ipaddr32 est une colonne de formule pour transformer 1.2.3.4 en 1<<24 + 2<<16 + 3<<8 + 4 pour un axe continu sur un graphique. le nom IP sera recherché plus tard. kind2 est une formule permettant de réduire tous les différents identifiants d'utilisateur en un simple "login".
path = "f:/logs/";
list = Files In Directory( path, "recursive" );
global:dt = New Table( "logData",
New Column( "datetime", Numeric, "Continuous", Format( "ddMonyyyy:h:m:s", 18, 0 ), Input Format( "ddMonyyyy:h:m:s", 0 )),
New Column( "ipaddr", Character, "Nominal" ),
New Column( "kind", Character, "Nominal" ),
New Column( "detail1", Character, "Nominal" ),
New Column( "detail2", Character, "Nominal" )
);
Analyse efficace
Pour une vitesse maximale avec des fichiers volumineux, l'analyse JSL doit être effectuée en un seul appel de fonction PatMatch. Chaque appel à PatMatch entraîne une certaine surcharge, et si vous effectuez plusieurs appels, vous coupez probablement également la chaîne d'entrée au fur et à mesure, ce qui ajoute encore plus de surcharge. Alors, faisons-le avec un PatMatch par fichier journal. Les clés sont deux fonctions de correspondance de modèles : PatRepeat et PatTest. PatRepeat laissera le motif se répéter pour chaque ligne du fichier. PatTest laissera le modèle exécuter du JSL pendant le match (nominalement pour tester une condition, mais le résultat de notre test sera toujours "vrai"). Quelques modèles d'aide, et puis passons aux bonnes choses...
ipPattern = patRegex("(\d{1,3}[-.]\d{1,3}[-.]\d{1,3}[-.]\d{1,3})|([a-fA-F0-9:]+)"); // approximation
ipMonth=patRegex("Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec");
datePattern1 = "[" + patregex("\d{2}")>>day + "/" + ipMonth>>month + "/" + patregex("\d{4}")>>year + ":" + patregex("\d\d:\d\d:\d\d")>>time + patregex(" [-+]\d{4}") +"]"; // [18/Sep/2016:06:14:25 -0400]
datePattern2 = ipMonth>>month + patspan(" ") + patregex("\d{1,2}")>>day + " " + patregex("\d\d:\d\d:\d\d")>>time + pattest(year="2016";1); //vs Oct 16 04:54:31 the unknown year is 2016 for these logs
quote = "\!"";
quotepattern = quote + patrepeat(patbreak(quote||"\") +(("\"+(quote|""))|"")) + quote; // allows \" escaped embedded quotation marks
les deux modèles de date correspondent aux deux formats de date. L'opérateur >> place le texte correspondant à gauche dans la variable à droite. PatTest(year="2016";1) renvoie toujours true après avoir attribué 2016 à l'année ; ceci concerne les fichiers journaux sans année explicite. J'utilise un mélange de modèles et d'expressions régulières pour créer des modèles de plus en plus grands. Voici le modèle Apache access.log :
apachePat = ipPattern>>ip+" "+patbreak(" ")+" "+patbreak(" ")+" "
+datePattern1+" "
+quotePattern>>request+" "
+patbreak(" ")>>code+" "
+patbreak(" ")>>length+" "+patbreak(" ")+" "
+(quotePattern|patbreak("\!n\!r"))>>browser
+pattest(
dt1<<ADDROWS></ADDROWS>
Comparez ce que cela fait à une ligne du access.log
192.168.1.1 - - [30/Aug/2016:08:51:52 -0400] "GET /tmUnblock.cgi HTTP/1.0" 404 495 "-" "Wget(linux)"
et remarquez le pattest(...) à la fin ; il ajoute une ligne au tableau dt1 et remplit les colonnes avec les valeurs qui viennent d'être analysées.
Il existe également des modèles UFW et d'authentification. Voici la fonction pour analyser un fichier :
global:skipPattern = (patbreak("\!n\!r") + patlen(1))>> unknown + pattest(show(filename,unknown);stop();1);
global:whitespace = patregex("\s*");
parser = Function( {filename, pattern, dt1}, {Default Local},
txt = Load Text File( filename );
rc = Pat Match( //
txt, //
Pat Pos( 0 ) //
+ Pat Repeat( ((global:whitespace + pattern + global:whitespace) | global:skipPattern) + Pat Fence() ) //
+ Pat R Pos( 0 ) //
); //
);
La fonction charge un journal à partir du nom de fichier et utilise le paramètre pattern pour l'analyser et stocke les résultats dans dt1. La fonction PatMatch crée un modèle d'alternance qui permet d'ignorer les lignes non reconnues. PatFence() est une autre optimisation pour les gros fichiers ; cela signifie "si vous êtes arrivé jusqu'ici, cela ne sert à rien de sauvegarder et d'essayer quelque chose de différent", et cela peut à la fois accélérer la correspondance et réduire la mémoire requise. Enfin, voici le pilote de la fonction :
While( N Items( file = Remove From( list, 1 ) ) == 1, //
file = file[1];//
If(//
!Is Missing( Regex( file, "ssl_access.log" ) ), //
parser( path || file, apachePat, global:dt );//
, // else
!Is Missing( Regex( file, "other_vhosts_access.log" ) ), //
0//parser( path || file, apachePat, global:dt );//
, // else
!Is Missing( Regex( file, "access.log" ) ), //
parser( path || file, apachePat, global:dt );//
, // else
!Is Missing( Regex( file, "ufw.log" ) ),
parser( path || file, ufwPat, global:dt );//
, // else
!Is Missing( Regex( file, "auth.log" ) ), //
parser( path || file, authPat, global:dt );//
);//
);//
Points d'interêts
L'ordinateur Raspberry PI semble attirer de nombreuses attaques utilisant le nom d'utilisateur PI. Les scripts PHP doivent être mal configurés assez souvent ; il y a beaucoup de vérifications pour certains fichiers "admin" PHP. Et trouver un port ouvert qui présente une faiblesse doit être vraiment précieux ; la grande majorité des attaques consistent à tester des ports. Si vous gérez un serveur Web et que vous n'utilisez pas de règles fail2ban et de pare-feu (comme UFW), vous devriez envisager de les installer et de les configurer. Vous pouvez ralentir une attaque par force brute en autorisant quelques tentatives par jour au lieu de plusieurs tentatives par seconde.
Dashboard view of web trafficVue du tableau de bord du trafic Web