├── 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