This is an automated email from the git hooks/post-receive script. New commit to branch develop in repository mum. See http://git.chorem.org/mum.git commit 91d6baf751607759cf9c762bd21e5373b95b2298 Author: Alexis Guilbaud <guilbaud@codelutin.com> Date: Tue Feb 17 11:29:06 2015 +0100 scan nmap sauvegardé dans bdd Shelve --- app/modules/detection_modules/__init__.py | 1 + app/modules/detection_modules/drive_detection.py | 34 +++++ app/modules/detection_modules/nagios_detection.py | 1 + app/modules/detection_modules/nmap_detection.py | 160 ++++++++++++++++++++++ app/modules/detection_modules/snmp_detection.py | 1 + app/modules/detection_modules/ssh_detection.py | 103 ++++++++++++++ 6 files changed, 300 insertions(+) diff --git a/app/modules/detection_modules/__init__.py b/app/modules/detection_modules/__init__.py new file mode 100644 index 0000000..fcb43f2 --- /dev/null +++ b/app/modules/detection_modules/__init__.py @@ -0,0 +1 @@ +__author__ = 'aguilbaud' diff --git a/app/modules/detection_modules/drive_detection.py b/app/modules/detection_modules/drive_detection.py new file mode 100644 index 0000000..fb374b3 --- /dev/null +++ b/app/modules/detection_modules/drive_detection.py @@ -0,0 +1,34 @@ +# -*- coding: utf8 -*- +from __future__ import unicode_literals +__author__ = 'aguilbaud' + +''' +Retourne les informations des partitions systeme sous la forme : +{"sr0": {"mountpoint": "none", "type": "rom", "name": "sr0", "size": "1024M"} +''' + +# Informations sur les partitions +def detect_drives(conn, os): + cmd = "lsblk -r --output=NAME,SIZE,TYPE,MOUNTPOINT" + stdin, stdout, stderr = conn.exec_command(cmd) + dict_total = {} + i = 1 + ignore = True + for line in stdout.read().splitlines(): + # On ignore la premiere ligne qui ne contient pas de valeurs + if ignore: + ignore = False + else: + dict_drive = {} + tab_elem = line.split() + dict_drive["name"] = tab_elem[0] + dict_drive["size"] = tab_elem[1] + dict_drive["type"] = tab_elem[2] + if len(tab_elem) > 3: + dict_drive["mountpoint"] = tab_elem[3] + else: + dict_drive["mountpoint"] = "none" + # meilleur nom pour chaque attribut ? + dict_total[dict_drive["name"]] = dict_drive + i = i + 1 + return json.dumps(dict_total) \ No newline at end of file diff --git a/app/modules/detection_modules/nagios_detection.py b/app/modules/detection_modules/nagios_detection.py new file mode 100644 index 0000000..fcb43f2 --- /dev/null +++ b/app/modules/detection_modules/nagios_detection.py @@ -0,0 +1 @@ +__author__ = 'aguilbaud' diff --git a/app/modules/detection_modules/nmap_detection.py b/app/modules/detection_modules/nmap_detection.py new file mode 100644 index 0000000..d5a95c1 --- /dev/null +++ b/app/modules/detection_modules/nmap_detection.py @@ -0,0 +1,160 @@ +# -*- coding: utf8 -*- +from __future__ import unicode_literals +__author__ = 'aguilbaud' + +from xml.dom import minidom +import pexpect +import json +import shelve +import os.path +from .. import storage + +scanned_ip = {} + + +def get_scanned_ip(): + global scanned_ip + return json.dumps(scanned_ip) + + +# fonction qui permet de decomposer les differentes plages d'ip +# lance la detection nmap pour chacune des ip comprises dans cette plage +# NB : il est possible de lancer nmap directement sur la plage, mais il est alors difficile de determiner l'état +# du scan. De plus, si la plage d'ip est grande, l'exécution sera bien plus longue et tous les résultats +# seront obtenus d'un coup à la fin. +def check_ip_range(ip_range, ws): + # separation des 4 octets + range_byte_1 = ip_range.split('.')[0] + range_byte_2 = ip_range.split('.')[1] + range_byte_3 = ip_range.split('.')[2] + range_byte_4 = ip_range.split('.')[3] + + # separation des plages eventuelles + split_byte_1 = range_byte_1.split('-') + split_byte_2 = range_byte_2.split('-') + split_byte_3 = range_byte_3.split('-') + split_byte_4 = range_byte_4.split('-') + + # si aucune plage n'est indiquee, on cree une plage de meme valeur + if len(split_byte_1) == 1: + split_byte_1.append(split_byte_1[0]) + # verification que les nombres sont ordonnes correctement + # et que les valeurs entrees sont inferieures a 255 + split_byte_1 = check_order_and_under_255(split_byte_1) + + # idem pour le deuxieme octet + if len(split_byte_2) == 1: + split_byte_2.append(split_byte_2[0]) + split_byte_2 = check_order_and_under_255(split_byte_2) + + # idem pour le troisieme octet + if len(split_byte_3) == 1: + split_byte_3.append(split_byte_3[0]) + split_byte_3 = check_order_and_under_255(split_byte_3) + + # idem pour le quatrieme octet + if len(split_byte_4) == 1: + split_byte_4.append(split_byte_4[0]) + split_byte_4 = check_order_and_under_255(split_byte_4) + + # il est possible d'ajouter ici une condition pour verifier que l'utilisateur + # n'ait pas entre une plage d'IP trop grande + + # pour toutes les plages dans l'ordre croissant, en partant du dernier octet + for byte_1 in range(int(split_byte_1[0]), int(split_byte_1[1]) + 1): + for byte_2 in range(int(split_byte_2[0]), int(split_byte_2[1]) + 1): + for byte_3 in range(int(split_byte_3[0]), int(split_byte_3[1]) + 1): + for byte_4 in range(int(split_byte_4[0]), int(split_byte_4[1]) + 1): + launch_detection(byte_1, byte_2, byte_3, byte_4, ws) + + +# vérifie que la plage de données entrée est dans l'ordre croissant +# et que ses valeurs sont inferieures à 255 +# si ce n'est pas le cas, retourne le tableau trié et/ou avec les valeurs capées à 255 +def check_order_and_under_255(tab_val): + if int(tab_val[0]) > 255 : + tab_val[0] = '255' + if int(tab_val[1]) > 255 : + tab_val[1] = '255' + if int(tab_val[0]) > int(tab_val[1]): + tmp = tab_val[1] + tab_val[1] = tab_val[0] + tab_val[0] = tmp + return tab_val + + +# lance la detection a l'aide de nmap sur l'ip representee par les 4 octets passes en parametres +def launch_detection(byte_1, byte_2, byte_3, byte_4, ws): + req = {} + ip = str(byte_1) + '.' + str(byte_2) + '.' + str(byte_3) + '.' + str(byte_4) + req["30"] = "Scan de l'IP " + ip + " en cours..." + ws.send(json.dumps(req)) + child = pexpect.spawn('nmap', ['-A', ip, '-oX', 'res.xml']) + res = '' + # ici : possibilite de verifier l'avancement du scan, si option verbose (-v3) activee dans la commande nmap + try: + while child.isalive(): + child.expect('Completed', timeout=None) + res += child.before + '<br/>' + except pexpect.EOF: + res += ' A FINI' + try: + parse_res(ip) + except: + del req["30"] + req["40"] = "Database error" + except pexpect.TIMEOUT: + del req["30"] + req["40"] = "Timeout on nmap execution" + ws.send(json.dumps(req)) + return res + + +# parse le resultat xml de nmap pour ne conserver que les valeurs interssantes +# envoie directement le resultat sur le service ElasticSearch +def parse_res(ip): + global scanned_ip + # Ouverture du fichier xml avec le parseur minidom + root = minidom.parse("res.xml") + collection = root.documentElement + + # Recuperer tous les <host> de la collection + hosts = collection.getElementsByTagName("host") + + # Recuperation des noeuds de chaque <host> et affichage de leur attributss + # JSON = liste de dictionnaires + for host in hosts: + status = host.getElementsByTagName('status')[0] + address = host.getElementsByTagName('address')[0] + + dict_host = {} + dict_host['addr'] = address.getAttribute('addr') + dict_host['date'] = host.getAttribute('endtime') + dict_host['state'] = status.getAttribute('state') + dict_host['os'] = 'unknown' # par defaut + + hostnames_elem = host.getElementsByTagName('hostnames')[0] + hostnames = hostnames_elem.getElementsByTagName('hostname') + for hostname in hostnames: + dict_host['hostname'] = hostname.getAttribute("name") + + ports_elem = host.getElementsByTagName('ports')[0] + ports = ports_elem.getElementsByTagName('port') + list_dict_port = [] + for port in ports: + dict_port = {} + state = port.getElementsByTagName('state')[0] + service = port.getElementsByTagName('service')[0] + if service.hasAttribute("ostype"): + dict_host['os'] = service.getAttribute("ostype") + if state.getAttribute('state') == 'open': + dict_port['portid'] = port.getAttribute('portid') + dict_port['portname'] = service.getAttribute('name') + # Ajouter d'autres infos ? + list_dict_port.append(dict_port) + dict_host['openports'] = list_dict_port + # sauvegarde de l'host dans la base elasticsearch avec pour ID son IP + storage.add_host(dict_host['addr'], json.dumps(dict_host)) + pexpect.run("rm -f res.xml") + #pexpect.run('curl -XPUT \'localhost:9200/saved_hosts' + scanned_ip[ip] = "localhost:9200/host/external/" + ip \ No newline at end of file diff --git a/app/modules/detection_modules/snmp_detection.py b/app/modules/detection_modules/snmp_detection.py new file mode 100644 index 0000000..fcb43f2 --- /dev/null +++ b/app/modules/detection_modules/snmp_detection.py @@ -0,0 +1 @@ +__author__ = 'aguilbaud' diff --git a/app/modules/detection_modules/ssh_detection.py b/app/modules/detection_modules/ssh_detection.py new file mode 100644 index 0000000..c2d1c52 --- /dev/null +++ b/app/modules/detection_modules/ssh_detection.py @@ -0,0 +1,103 @@ +import paramiko +import json + + +# il faudrait p-e separer le traitement du resultat des commandes avec le lancement en lui mm +# si independant des protocoles utilises + +def connect(): + # TODO rendre la connection dynamique + key = paramiko.RSAKey.from_private_key_file("/home/aguilbaud/.ssh/id_rsa") + + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect('127.0.0.1', username='aguilbaud', pkey=key) + + run_detection(ssh) + + disconnect(ssh) + + +def disconnect(ssh): + ssh.close() + + +def run_detection(ssh): + print detect_hardware(ssh) + print detect_drives(ssh) + print detect_os_version(ssh) + print detect_non_updated_packages(ssh) + + +# Informations materielles globales +def detect_hardware(ssh): + cmd = "lshw -json" + stdin, stdout, stderr = ssh.exec_command(cmd) + res = "" + for line in stdout.read().splitlines(): + res += line + res_json = json.loads(res) + # TODO Traitement du resultat pour garder l'essentiel... + return res_json + + +# Informations sur les partitions +def detect_drives(ssh): + cmd = "lsblk -r --output=NAME,SIZE,TYPE,MOUNTPOINT" + stdin, stdout, stderr = ssh.exec_command(cmd) + dict_total = {} + i = 1 + ignore = True + for line in stdout.read().splitlines(): + # On ignore la premiere ligne qui ne contient pas de valeurs + if ignore: + ignore = False + else: + dict_drive = {} + tab_elem = line.split() + dict_drive["name"] = tab_elem[0] + dict_drive["size"] = tab_elem[1] + dict_drive["type"] = tab_elem[2] + if len(tab_elem) > 3: + dict_drive["mountpoint"] = tab_elem[3] + else: + dict_drive["mountpoint"] = "none" + # meilleur nom pour chaque attribut ? + dict_total[dict_drive["name"]] = dict_drive + i = i + 1 + return json.dumps(dict_total) + + +def detect_os_version(ssh): + # kernel + cmd = "cat /proc/version" + stdin, stdout, stderr = ssh.exec_command(cmd) + res = stdout.read() + dict_total = {} + dict_total["kernel"] = res.split('#')[0] + + # os + cmd = "cat /etc/os-release" + stdin, stdout, stderr = ssh.exec_command(cmd) + for line in stdout.read().splitlines(): + tab_elem = line.split("=") + # pour retirer les "" sur tous les champs qui en possedent + tab_right = tab_elem[1].split('"') + if len(tab_right) == 1: + dict_total[str.lower(tab_elem[0])] = tab_right[0] + else: + dict_total[str.lower(tab_elem[0])] = tab_right[1] + # encore une fois, on recupere tout le contenu de la commande, p-e qu'il est possible d'enlever le superflu + return json.dumps(dict_total, indent=4, separators=(',', ': ')) + + +# dependant de la langue du systeme ? +def detect_non_updated_packages(ssh): + cmd = "apt-get upgrade -s" + stdin, stdout, stderr = ssh.exec_command(cmd) + res = stdout.read() + tab_res = res.split(':') + if len(tab_res) == 2: + return json.dumps({'non_updated_packages': False}) + else: + return json.dumps({'non_updated_packages': True}) \ No newline at end of file -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.