├── ElasticsearchPlugin.py ├── README.md ├── libnmap ├── __init__.py ├── diff.py ├── objects │ ├── __init__.py │ ├── cpe.py │ ├── host.py │ ├── os.py │ ├── report.py │ └── service.py ├── parser.py ├── plugins │ ├── __init__.py │ ├── backend_host.py │ ├── backend_service.py │ ├── backendplugin.py │ ├── backendpluginFactory.py │ ├── es.py │ ├── mongodb.py │ ├── s3.py │ └── sql.py ├── process.py └── reportjson.py ├── run.py ├── src ├── flower.tar.gz ├── supervisord_client.conf └── supervisord_server.conf ├── tasks.py ├── wyfunc.py └── wyportmap.py /ElasticsearchPlugin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from libnmap.parser import NmapParser 4 | from libnmap.reportjson import ReportDecoder 5 | from libnmap.plugins.es import NmapElasticsearchPlugin 6 | from datetime import datetime 7 | import json 8 | 9 | nmap_report = NmapParser.parse_fromfile('libnmap/test/files/1_hosts.xml') 10 | mindex = datetime.fromtimestamp(nmap_report.started).strftime('%Y-%m-%d') 11 | db = NmapElasticsearchPlugin(index=mindex) 12 | dbid = db.insert(nmap_report) 13 | nmap_json = db.get(dbid) 14 | 15 | nmap_obj = json.loads(json.dumps(nmap_json), cls=ReportDecoder) 16 | print(nmap_obj) 17 | #print(db.getall()) 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # thorns 2 | thorns_project 分布式异步队列系统 3 | 4 | 运行流程 5 | ----------------------------------- 6 | * 启动redis内存服务器,作为队列存储数据库使用 7 | * 配置芹菜(celery)运行环境,并连接redis队列内存,读取执行任务,并返回结果存储到后端MySQL数据库 8 | * 配置任务控制台花花(flower),并连接redis队列内存,管理所有worker客户端与执行的任务队列 9 | * 通过run.py脚本调用celery向队列压入任务 10 | * 通过flower的http api脚本调用api向队列压入任务 11 | * 任务执行的结果自动存入后端数据库 12 | 13 | 反馈 14 | ----------------------------------- 15 | > 微博:http://weibo.com/ringzero
16 | > 邮箱:ringzero@0x557.org
17 | 18 | 运行环境 19 | ----------------------------------- 20 | * CentOS、Kali Linux、Ubuntu、Debian 21 | * Python 2.7.x 22 | * Redis 23 | * MysQL 24 | * Celery 25 | * Tornado 26 | * Supervisord 27 | 28 | 安装配置说明 29 | ----------------------------------- 30 | ## CentOS 服务端 31 | 32 | #### 安装 Redis-Server 33 | $ wget http://download.redis.io/releases/redis-2.8.19.tar.gz 34 | $ tar xzf redis-2.8.19.tar.gz 35 | $ cd redis-2.8.19 36 | $ make 37 | $ sudo cp src/redis-server /usr/bin/ 38 | $ sudo cp redis.conf /etc/redis.conf 39 | /* 修改 /etc/redis.conf 37行,将daemonize no改为daemonize yes,让redis后台运行 */ 40 | $ sudo vim /etc/redis.conf 41 | daemonize yes 42 | # 启动Redis-Server 43 | $ sudo redis-server /etc/redis.conf 44 | 45 | #### 安装 pip 46 | $ wget https://pypi.python.org/packages/source/p/pip/pip-6.0.8.tar.gz 47 | $ tar zvxf pip-6.0.8.tar.gz 48 | $ cd pip-6.0.8 49 | $ sudo python setup.py install 50 | 51 | #### 安装 MySQL-python 52 | $ sudo yum -y install python-devel mysql-devel subversion-devel 53 | $ sudo pip install MySQL-python SQLAlchemy 54 | 55 | #### 安装 Celery 56 | $ sudo pip install -U celery[redis] 57 | 58 | #### 安装 Flower & 下载thorns运行环境代码 59 | $ cd /home/ 60 | $ sudo yum -y install git 61 | $ git clone https://github.com/ring04h/thorns.git 62 | $ cd /home/thorns/src 63 | $ tar zvxf flower.tar.gz 64 | $ cd flower-0.7.3 65 | $ python setup.py install 66 | /* 启动Flower 这里的redis ip可以配置为你的外网的IP */ 67 | $ celery flower --port=8080 --broker=redis://127.0.0.1:6379/0 & 68 | 建议使用Supervisord的守护进程来启动Flower,确保系统7*24小时的稳定性 69 | 70 | #### 安装 Supervisord 71 | $ sudo pip install supervisor 72 | $ sudo cp /home/thorns/src/supervisord_server.conf /etc/supervisord.conf 73 | /* 修改 /etc/supervisord.conf 141行 修改redis ip为你自己的ip --broker=redis://127.0.0.1:6379/0 */ 74 | /* 修改 /etc/supervisord.conf 153行 修改programe为你想定义的worker名称 [program:worker-ringzero] */ 75 | $ sudo vim /etc/supervisord.conf 76 | /* 启动 supervisord */ 77 | $ supervisord -c /etc/supervisord.conf 78 | http://127.0.0.1:9001/ 可以在线守护管理thorns的进程,实现远程重启 79 | 80 | #### 检查各服务是否正常启动后,开始配置客户端任务脚本 81 | 1、http://youip:8080/ thorns 控制台 82 | 2、http://youip:9001/ supervisord 控制台 83 | 3、修改tasks.py内的芹菜配置 84 | 对应你自己的redis-server服务器IP 85 | BROKER_URL = 'redis://120.132.54.90:6379/0', 86 | 对应你自己的MySQL-server服务器IP 87 | CELERY_RESULT_BACKEND = 'db+mysql://celery:celery1@3Wscan@42.62.52.62:443/wscan', 88 | 89 | 配置完毕后,就可以部署多台客户端进行分布式任务执行了 90 | 91 | ## CentOS 客户端(建议大规模部署) 92 | # 安装 git & 下载 thorns_project 93 | $ sudo yum -y install git 94 | $ cd /home/ 95 | $ git clone https://github.com/ring04h/thorns.git 96 | 97 | # 安装 pip 98 | $ wget https://pypi.python.org/packages/source/p/pip/pip-6.0.8.tar.gz 99 | $ tar zvxf pip-6.0.8.tar.gz 100 | $ cd pip-6.0.8 101 | $ sudo python setup.py install 102 | 103 | # 安装 MySQL-python 104 | $ sudo yum -y install python-devel mysql-devel subversion-devel 105 | $ sudo pip install MySQL-python SQLAlchemy 106 | 107 | # 安装 nmap 108 | # 32位系统 109 | $ sudo rpm -vhU https://nmap.org/dist/nmap-6.47-1.i386.rpm 110 | # 64位系统 111 | $ sudo rpm -vhU https://nmap.org/dist/nmap-6.47-1.x86_64.rpm 112 | 113 | # 安装 Celery 114 | $ sudo pip install -U celery[redis] 115 | 116 | # 安装 Supervisord 117 | $ sudo pip install supervisor 118 | $ sudo cp /home/thorns/src/supervisord_client.conf /etc/supervisord.conf 119 | /* 修改 /etc/supervisord.conf 140行 修改programe为你想定义的worker名称 [program:worker-ringzero] */ 120 | $ sudo vim /etc/supervisord.conf 121 | /* 启动 supervisord */ 122 | $ supervisord -c /etc/supervisord.conf 123 | http://127.0.0.1:9001/ 可以在线守护管理thorns的进程,实现远程重启 124 | 125 | # 修改tasks.py内的芹菜配置(分布式任务关键配置项) 126 | 对应你自己的redis-server服务器IP 127 | BROKER_URL = 'redis://120.132.54.90:6379/0', 128 | 对应你自己的MySQL-server服务器IP 129 | CELERY_RESULT_BACKEND = 'db+mysql://celery:celery1@3Wscan@42.62.52.62:443/wscan', 130 | 131 | # 环境搭建完毕,这时候访问thorns project的控制台,就会发现worker客户端已经出现在那里 132 | 演示地址:http://thorns.wuyun.org:8080/ 133 | 你的请访问:http://youip:8080/ 134 | 135 | 136 | 使用说明(可客户端发起任务也可http api发起任务) 137 | ----------------------------------- 138 | #### 命令行调用 139 | 在你的任意一台worker客户端,或者thorns服务端 140 | $ cd /home/thorns/ 141 | $ python run.py 42.62.52.1-42.62.62.254 188 142 | $ python run.py 42.62.52.1-254 189 143 | 均可以向redis压入nmap扫描任务,worker客户端的分布式集群会自动分发任务执行,并存储到后台数据库 144 | 记得修改wyportmap.py里面的扫描结果,存到你自己的数据库 145 | 146 | reinhard-mbp:thorns reinhard$ python run.py 42.62.52.1-254 189 147 | -------------------------------------------------- 148 | * push 42.62.52.1 to Redis 149 | * AsyncResult:23147d02-294d-41e5-84e5-5e1b15e72fc4 150 | -------------------------------------------------- 151 | * push 42.62.52.2 to Redis 152 | * AsyncResult:542984a4-4434-475f-9a62-bfc81206ea57 153 | -------------------------------------------------- 154 | * push 42.62.52.3 to Redis 155 | * AsyncResult:7d005661-d719-41ef-babc-4c853b2c49cc 156 | -------------------------------------------------- 157 | * push 42.62.52.4 to Redis 158 | * AsyncResult:ddcf9486-09d9-4dd2-9bb4-2618e6a161b8 159 | -------------------------------------------------- 160 | 161 | wyportmap相关帮助: https://github.com/ring04h/wyportmap 162 | 163 | #### HTTP API 远程调用 164 | 重启 worker 线程池: 165 | $ curl -X POST http://thorns.wuyun.org:8080/api/worker/pool/restart/myworker 166 | 167 | 远程调用HTTP API启动一个nmap扫描任务: 168 | $ curl -X POST -d '{"args":["42.62.52.62",2222]}' http://thorns.wuyun.org:8080/api/task/send-task/tasks.nmap_dispath 169 | 170 | 强制结束一个正在执行的任务: 171 | $ curl -X POST -d 'terminate=True' http://thorns.wuyun.org:8088/api/task/revoke/a9361c1b-fd1d-4f48-9be2-8656a57e906b 172 | 173 | -------------------------------------------------------------------------------- /libnmap/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __author__ = 'Ronald Bister, Mike Boutillier' 4 | __credits__ = ['Ronald Bister', 'Mike Boutillier'] 5 | __maintainer__ = 'Ronald Bister' 6 | __email__ = 'mini.pelle@gmail.com' 7 | __license__ = 'CC-BY' 8 | __version__ = '0.6.1' 9 | -------------------------------------------------------------------------------- /libnmap/diff.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | class DictDiffer(object): 5 | """ 6 | Calculate the difference between two dictionaries as: 7 | (1) items added 8 | (2) items removed 9 | (3) keys same in both but changed values 10 | (4) keys same in both and unchanged values 11 | """ 12 | def __init__(self, current_dict, past_dict): 13 | self.current_dict = current_dict 14 | self.past_dict = past_dict 15 | self.set_current = set(current_dict.keys()) 16 | self.set_past = set(past_dict.keys()) 17 | self.intersect = self.set_current.intersection(self.set_past) 18 | 19 | def added(self): 20 | return self.set_current - self.intersect 21 | 22 | def removed(self): 23 | return self.set_past - self.intersect 24 | 25 | def changed(self): 26 | return (set(o for o in self.intersect 27 | if self.past_dict[o] != self.current_dict[o])) 28 | 29 | def unchanged(self): 30 | return (set(o for o in self.intersect 31 | if self.past_dict[o] == self.current_dict[o])) 32 | 33 | 34 | class NmapDiff(DictDiffer): 35 | """ 36 | NmapDiff compares two objects of same type to enable the user to check: 37 | 38 | - what has changed 39 | - what has been added 40 | - what has been removed 41 | - what was kept unchanged 42 | 43 | NmapDiff inherit from DictDiffer which makes the actual comparaison. 44 | The different methods from DictDiffer used by NmapDiff are the 45 | following: 46 | 47 | - NmapDiff.changed() 48 | - NmapDiff.added() 49 | - NmapDiff.removed() 50 | - NmapDiff.unchanged() 51 | 52 | Each of the returns a python set() of key which have changed in the 53 | compared objects. To check the different keys that could be returned, 54 | refer to the get_dict() method of the objects you which to 55 | compare (i.e: libnmap.objects.NmapHost, NmapService,...). 56 | """ 57 | def __init__(self, nmap_obj1, nmap_obj2): 58 | """ 59 | Constructor of NmapDiff: 60 | 61 | - Checks if the two objects are of the same class 62 | - Checks if the objects are "comparable" via a call to id() (dirty) 63 | - Inherits from DictDiffer and 64 | """ 65 | if(nmap_obj1.__class__ != nmap_obj2.__class__ or 66 | nmap_obj1.id != nmap_obj2.id): 67 | raise NmapDiffException("Comparing objects with non-matching id") 68 | 69 | self.object1 = nmap_obj1.get_dict() 70 | self.object2 = nmap_obj2.get_dict() 71 | 72 | DictDiffer.__init__(self, self.object1, self.object2) 73 | 74 | def __repr__(self): 75 | return ("added: [{0}] -- changed: [{1}] -- " 76 | "unchanged: [{2}] -- removed [{3}]".format(self.added(), 77 | self.changed(), 78 | self.unchanged(), 79 | self.removed())) 80 | 81 | 82 | class NmapDiffException(Exception): 83 | def __init__(self, msg): 84 | self.msg = msg 85 | -------------------------------------------------------------------------------- /libnmap/objects/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from libnmap.objects.report import NmapReport 4 | from libnmap.objects.host import NmapHost 5 | from libnmap.objects.service import NmapService 6 | 7 | __all__ = ['NmapReport', 'NmapHost', 'NmapService'] 8 | -------------------------------------------------------------------------------- /libnmap/objects/cpe.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | class CPE(object): 5 | """ 6 | CPE class offers an API for basic CPE objects. 7 | These objects could be found in NmapService or in tag 8 | within NmapHost. 9 | 10 | :todo: interpret CPE string and provide appropriate API 11 | """ 12 | def __init__(self, cpestring): 13 | self._cpestring = cpestring 14 | self.cpedict = {} 15 | 16 | zk = ['cpe', 'part', 'vendor', 'product', 'version', 17 | 'update', 'edition', 'language'] 18 | self._cpedict = dict((k, '') for k in zk) 19 | splitup = cpestring.split(':') 20 | self._cpedict.update(dict(zip(zk, splitup))) 21 | 22 | @property 23 | def cpestring(self): 24 | """ 25 | Accessor for the full CPE string. 26 | """ 27 | return self._cpestring 28 | 29 | def __repr__(self): 30 | return self._cpestring 31 | 32 | def get_part(self): 33 | """ 34 | Returns the cpe part (/o, /h, /a) 35 | """ 36 | return self._cpedict['part'] 37 | 38 | def get_vendor(self): 39 | """ 40 | Returns the vendor name 41 | """ 42 | return self._cpedict['vendor'] 43 | 44 | def get_product(self): 45 | """ 46 | Returns the product name 47 | """ 48 | return self._cpedict['product'] 49 | 50 | def get_version(self): 51 | """ 52 | Returns the version of the cpe 53 | """ 54 | return self._cpedict['version'] 55 | 56 | def get_update(self): 57 | """ 58 | Returns the update version 59 | """ 60 | return self._cpedict['update'] 61 | 62 | def get_edition(self): 63 | """ 64 | Returns the cpe edition 65 | """ 66 | return self._cpedict['edition'] 67 | 68 | def get_language(self): 69 | """ 70 | Returns the cpe language 71 | """ 72 | return self._cpedict['language'] 73 | 74 | def is_application(self): 75 | """ 76 | Returns True if cpe describes an application 77 | """ 78 | return (self.get_part() == '/a') 79 | 80 | def is_hardware(self): 81 | """ 82 | Returns True if cpe describes a hardware 83 | """ 84 | return (self.get_part() == '/h') 85 | 86 | def is_operating_system(self): 87 | """ 88 | Returns True if cpe describes an operating system 89 | """ 90 | return (self.get_part() == '/o') 91 | -------------------------------------------------------------------------------- /libnmap/objects/host.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from libnmap.diff import NmapDiff 4 | from libnmap.objects.os import NmapOSFingerprint 5 | 6 | 7 | class NmapHost(object): 8 | """ 9 | NmapHost is a class representing a host object of NmapReport 10 | """ 11 | def __init__(self, starttime='', endtime='', address=None, status=None, 12 | hostnames=None, services=None, extras=None): 13 | """ 14 | NmapHost constructor 15 | :param starttime: unix timestamp of when the scan against 16 | that host started 17 | :type starttime: string 18 | :param endtime: unix timestamp of when the scan against 19 | that host ended 20 | :type endtime: string 21 | :param address: dict ie :{'addr': '127.0.0.1', 'addrtype': 'ipv4'} 22 | :param status: dict ie:{'reason': 'localhost-response', 23 | 'state': 'up'} 24 | :return: NmapHost: 25 | """ 26 | self._starttime = starttime 27 | self._endtime = endtime 28 | self._hostnames = hostnames if hostnames is not None else [] 29 | self._status = status if status is not None else {} 30 | self._services = services if services is not None else [] 31 | self._extras = extras if extras is not None else {} 32 | self._osfingerprinted = False 33 | self.os = None 34 | if 'os' in self._extras: 35 | self.os = NmapOSFingerprint(self._extras['os']) 36 | self._osfingerprinted = True 37 | else: 38 | self.os = NmapOSFingerprint({}) 39 | 40 | self._ipv4_addr = None 41 | self._ipv6_addr = None 42 | self._mac_addr = None 43 | self._vendor = None 44 | for addr in address: 45 | if addr['addrtype'] == "ipv4": 46 | self._ipv4_addr = addr['addr'] 47 | elif addr['addrtype'] == 'ipv6': 48 | self._ipv6_addr = addr['addr'] 49 | elif addr['addrtype'] == 'mac': 50 | self._mac_addr = addr['addr'] 51 | if 'vendor' in addr: 52 | self._vendor = addr['vendor'] 53 | 54 | self._main_address = self._ipv4_addr or self._ipv6_addr or '' 55 | self._address = address 56 | 57 | def __eq__(self, other): 58 | """ 59 | Compare eq NmapHost based on : 60 | 61 | - hostnames 62 | - address 63 | - if an associated services has changed 64 | 65 | :return: boolean 66 | """ 67 | rval = False 68 | if(self.__class__ == other.__class__ and self.id == other.id): 69 | rval = (self.changed(other) == 0) 70 | return rval 71 | 72 | def __ne__(self, other): 73 | """ 74 | Compare ne NmapHost based on: 75 | 76 | - hostnames 77 | - address 78 | - if an associated services has changed 79 | 80 | :return: boolean 81 | """ 82 | rval = True 83 | if(self.__class__ == other.__class__ and self.id == other.id): 84 | rval = (self.changed(other) > 0) 85 | return rval 86 | 87 | def __repr__(self): 88 | """ 89 | String representing the object 90 | :return: string 91 | """ 92 | return "{0}: [{1} ({2}) - {3}]".format(self.__class__.__name__, 93 | self.address, 94 | " ".join(self._hostnames), 95 | self.status) 96 | 97 | def __hash__(self): 98 | """ 99 | Hash is needed to be able to use our object in sets 100 | :return: hash 101 | """ 102 | return (hash(self.status) ^ hash(self.address) ^ 103 | hash(frozenset(self._services)) ^ 104 | hash(frozenset(" ".join(self._hostnames)))) 105 | 106 | def changed(self, other): 107 | """ 108 | return the number of attribute who have changed 109 | :param other: NmapHost object to compare 110 | :return int 111 | """ 112 | return len(self.diff(other).changed()) 113 | 114 | def save(self, backend): 115 | 116 | if backend is not None: 117 | _id = backend.insert(self) 118 | else: 119 | raise RuntimeError 120 | return _id 121 | 122 | @property 123 | def starttime(self): 124 | """ 125 | Accessor for the unix timestamp of when the scan was started 126 | 127 | :return: string 128 | """ 129 | return self._starttime 130 | 131 | @property 132 | def endtime(self): 133 | """ 134 | Accessor for the unix timestamp of when the scan ended 135 | 136 | :return: string 137 | """ 138 | return self._endtime 139 | 140 | @property 141 | def address(self): 142 | """ 143 | Accessor for the IP address of the scanned host 144 | 145 | :return: IP address as a string 146 | """ 147 | return self._main_address 148 | 149 | @address.setter 150 | def address(self, addrdict): 151 | """ 152 | Setter for the address dictionnary. 153 | 154 | :param addrdict: valid dict is {'addr': '1.1.1.1', 155 | 'addrtype': 'ipv4'} 156 | """ 157 | if addrdict['addrtype'] == 'ipv4': 158 | self._ipv4_addr = addrdict['addr'] 159 | elif addrdict['addrtype'] == 'ipv6': 160 | self._ipv6_addr = addrdict['addr'] 161 | elif addrdict['addrtype'] == 'mac': 162 | self._mac_addr = addrdict['addr'] 163 | if 'vendor' in addrdict: 164 | self._vendor = addrdict['vendor'] 165 | 166 | self._main_address = self._ipv4_addr or self._ipv6_addr or '' 167 | self._address = addrdict 168 | 169 | @property 170 | def ipv4(self): 171 | """ 172 | Accessor for the IPv4 address of the scanned host 173 | 174 | :return: IPv4 address as a string 175 | """ 176 | return self._ipv4_addr or '' 177 | 178 | @property 179 | def mac(self): 180 | """ 181 | Accessor for the MAC address of the scanned host 182 | 183 | :return: MAC address as a string 184 | """ 185 | return self._mac_addr or '' 186 | 187 | @property 188 | def vendor(self): 189 | """ 190 | Accessor for the vendor attribute of the scanned host 191 | 192 | :return: string (vendor) of empty string if no vendor defined 193 | """ 194 | return self._vendor or '' 195 | 196 | @property 197 | def ipv6(self): 198 | """ 199 | Accessor for the IPv6 address of the scanned host 200 | 201 | :return: IPv6 address as a string 202 | """ 203 | return self._ipv6_addr or '' 204 | 205 | @property 206 | def status(self): 207 | """ 208 | Accessor for the host's status (up, down, unknown...) 209 | 210 | :return: string 211 | """ 212 | return self._status['state'] 213 | 214 | @status.setter 215 | def status(self, statusdict): 216 | """ 217 | Setter for the status dictionnary. 218 | 219 | :param statusdict: valid dict is {"state": "open", 220 | "reason": "syn-ack", 221 | "reason_ttl": "0"} 222 | 'state' is the only mandatory key. 223 | """ 224 | self._status = statusdict 225 | 226 | def is_up(self): 227 | """ 228 | method to determine if host is up or not 229 | 230 | :return: bool 231 | """ 232 | rval = False 233 | if self.status == 'up': 234 | rval = True 235 | return rval 236 | 237 | @property 238 | def hostnames(self): 239 | """ 240 | Accessor returning the list of hostnames (array of strings). 241 | 242 | :return: array of string 243 | """ 244 | return self._hostnames 245 | 246 | @property 247 | def services(self): 248 | """ 249 | Accessor for the array of scanned services for that host. 250 | 251 | An array of NmapService objects is returned. 252 | 253 | :return: array of NmapService 254 | """ 255 | return self._services 256 | 257 | def get_ports(self): 258 | """ 259 | Retrieve a list of the port used by each service of the NmapHost 260 | 261 | :return: list: of tuples (port,'proto') ie:[(22,'tcp'),(25, 'tcp')] 262 | """ 263 | return [(p.port, p.protocol) for p in self._services] 264 | 265 | def get_open_ports(self): 266 | """ 267 | Same as get_ports() but only for open ports 268 | 269 | :return: list: of tuples (port,'proto') ie:[(22,'tcp'),(25, 'tcp')] 270 | """ 271 | return ([(p.port, p.protocol) 272 | for p in self._services if p.state == 'open']) 273 | 274 | def get_service(self, portno, protocol='tcp'): 275 | """ 276 | :param portno: int the portnumber 277 | :param protocol='tcp': string ('tcp','udp') 278 | 279 | :return: NmapService or None 280 | """ 281 | plist = [p for p in self._services if 282 | p.port == portno and p.protocol == protocol] 283 | if len(plist) > 1: 284 | raise Exception("Duplicate services found in NmapHost object") 285 | return plist.pop() if len(plist) else None 286 | 287 | def get_service_byid(self, service_id): 288 | """ 289 | Returns a NmapService by providing its id. 290 | 291 | The id of a nmap service is a python tupl made of (protocol, port) 292 | """ 293 | rval = None 294 | for _tmpservice in self._services: 295 | if _tmpservice.id == service_id: 296 | rval = _tmpservice 297 | return rval 298 | 299 | def os_class_probabilities(self): 300 | """ 301 | Returns an array of possible OS class detected during 302 | the OS fingerprinting. 303 | 304 | :return: Array of NmapOSClass objects 305 | """ 306 | rval = [] 307 | if self.os is not None: 308 | rval = self.os.osclasses 309 | return rval 310 | 311 | def os_match_probabilities(self): 312 | """ 313 | Returns an array of possible OS match detected during 314 | the OS fingerprinting 315 | 316 | :return: array of NmapOSMatches objects 317 | """ 318 | rval = [] 319 | if self.os is not None: 320 | rval = self.os.osmatches 321 | return rval 322 | 323 | @property 324 | def os_fingerprinted(self): 325 | """ 326 | Specify if the host has OS fingerprint data available 327 | 328 | :return: Boolean 329 | """ 330 | return self._osfingerprinted 331 | 332 | @property 333 | def os_fingerprint(self): 334 | """ 335 | Returns the fingerprint of the scanned system. 336 | 337 | :return: string 338 | """ 339 | rval = '' 340 | if self.os is not None: 341 | rval = "\n".join(self.os.fingerprints) 342 | return rval 343 | 344 | def os_ports_used(self): 345 | """ 346 | Returns an array of the ports used for OS fingerprinting 347 | 348 | :return: array of ports used: [{'portid': '22', 349 | 'proto': 'tcp', 350 | 'state': 'open'},] 351 | """ 352 | rval = [] 353 | try: 354 | rval = self._extras['os']['ports_used'] 355 | except (KeyError, TypeError): 356 | pass 357 | return rval 358 | 359 | @property 360 | def tcpsequence(self): 361 | """ 362 | Returns the difficulty to determine remotely predict 363 | the tcp sequencing. 364 | 365 | return: string 366 | """ 367 | rval = '' 368 | try: 369 | rval = self._extras['tcpsequence']['difficulty'] 370 | except (KeyError, TypeError): 371 | pass 372 | return rval 373 | 374 | @property 375 | def ipsequence(self): 376 | """ 377 | Return the class of ip sequence of the remote hosts. 378 | 379 | :return: string 380 | """ 381 | rval = '' 382 | try: 383 | rval = self._extras['ipidsequence']['class'] 384 | except (KeyError, TypeError): 385 | pass 386 | return rval 387 | 388 | @property 389 | def uptime(self): 390 | """ 391 | uptime of the remote host (if nmap was able to determine it) 392 | 393 | :return: string (in seconds) 394 | """ 395 | rval = 0 396 | try: 397 | rval = int(self._extras['uptime']['seconds']) 398 | except (KeyError, TypeError): 399 | pass 400 | return rval 401 | 402 | @property 403 | def lastboot(self): 404 | """ 405 | Since when the host was booted. 406 | 407 | :return: string 408 | """ 409 | rval = '' 410 | try: 411 | rval = self._extras['uptime']['lastboot'] 412 | except (KeyError, TypeError): 413 | pass 414 | return rval 415 | 416 | @property 417 | def distance(self): 418 | """ 419 | Number of hops to host 420 | 421 | :return: int 422 | """ 423 | rval = 0 424 | try: 425 | rval = int(self._extras['distance']['value']) 426 | except (KeyError, TypeError): 427 | pass 428 | return rval 429 | 430 | @property 431 | def scripts_results(self): 432 | """ 433 | Scripts results specific to the scanned host 434 | 435 | :return: array of