Repository: ring04h/thorns
Branch: master
Commit: 65dda993878e
Files: 28
Total size: 164.8 KB
Directory structure:
gitextract_4m8vr42h/
├── 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/
│ ├── supervisord_client.conf
│ └── supervisord_server.conf
├── tasks.py
├── wyfunc.py
└── wyportmap.py
================================================
FILE CONTENTS
================================================
================================================
FILE: ElasticsearchPlugin.py
================================================
#!/usr/bin/env python
from libnmap.parser import NmapParser
from libnmap.reportjson import ReportDecoder
from libnmap.plugins.es import NmapElasticsearchPlugin
from datetime import datetime
import json
nmap_report = NmapParser.parse_fromfile('libnmap/test/files/1_hosts.xml')
mindex = datetime.fromtimestamp(nmap_report.started).strftime('%Y-%m-%d')
db = NmapElasticsearchPlugin(index=mindex)
dbid = db.insert(nmap_report)
nmap_json = db.get(dbid)
nmap_obj = json.loads(json.dumps(nmap_json), cls=ReportDecoder)
print(nmap_obj)
#print(db.getall())
================================================
FILE: README.md
================================================
# thorns
thorns_project 分布式异步队列系统
运行流程
-----------------------------------
* 启动redis内存服务器,作为队列存储数据库使用
* 配置芹菜(celery)运行环境,并连接redis队列内存,读取执行任务,并返回结果存储到后端MySQL数据库
* 配置任务控制台花花(flower),并连接redis队列内存,管理所有worker客户端与执行的任务队列
* 通过run.py脚本调用celery向队列压入任务
* 通过flower的http api脚本调用api向队列压入任务
* 任务执行的结果自动存入后端数据库
反馈
-----------------------------------
> 微博:http://weibo.com/ringzero<br />
> 邮箱:ringzero@0x557.org<br />
运行环境
-----------------------------------
* CentOS、Kali Linux、Ubuntu、Debian
* Python 2.7.x
* Redis
* MysQL
* Celery
* Tornado
* Supervisord
安装配置说明
-----------------------------------
## CentOS 服务端
#### 安装 Redis-Server
$ wget http://download.redis.io/releases/redis-2.8.19.tar.gz
$ tar xzf redis-2.8.19.tar.gz
$ cd redis-2.8.19
$ make
$ sudo cp src/redis-server /usr/bin/
$ sudo cp redis.conf /etc/redis.conf
/* 修改 /etc/redis.conf 37行,将daemonize no改为daemonize yes,让redis后台运行 */
$ sudo vim /etc/redis.conf
daemonize yes
# 启动Redis-Server
$ sudo redis-server /etc/redis.conf
#### 安装 pip
$ wget https://pypi.python.org/packages/source/p/pip/pip-6.0.8.tar.gz
$ tar zvxf pip-6.0.8.tar.gz
$ cd pip-6.0.8
$ sudo python setup.py install
#### 安装 MySQL-python
$ sudo yum -y install python-devel mysql-devel subversion-devel
$ sudo pip install MySQL-python SQLAlchemy
#### 安装 Celery
$ sudo pip install -U celery[redis]
#### 安装 Flower & 下载thorns运行环境代码
$ cd /home/
$ sudo yum -y install git
$ git clone https://github.com/ring04h/thorns.git
$ cd /home/thorns/src
$ tar zvxf flower.tar.gz
$ cd flower-0.7.3
$ python setup.py install
/* 启动Flower 这里的redis ip可以配置为你的外网的IP */
$ celery flower --port=8080 --broker=redis://127.0.0.1:6379/0 &
建议使用Supervisord的守护进程来启动Flower,确保系统7*24小时的稳定性
#### 安装 Supervisord
$ sudo pip install supervisor
$ sudo cp /home/thorns/src/supervisord_server.conf /etc/supervisord.conf
/* 修改 /etc/supervisord.conf 141行 修改redis ip为你自己的ip --broker=redis://127.0.0.1:6379/0 */
/* 修改 /etc/supervisord.conf 153行 修改programe为你想定义的worker名称 [program:worker-ringzero] */
$ sudo vim /etc/supervisord.conf
/* 启动 supervisord */
$ supervisord -c /etc/supervisord.conf
http://127.0.0.1:9001/ 可以在线守护管理thorns的进程,实现远程重启
#### 检查各服务是否正常启动后,开始配置客户端任务脚本
1、http://youip:8080/ thorns 控制台
2、http://youip:9001/ supervisord 控制台
3、修改tasks.py内的芹菜配置
对应你自己的redis-server服务器IP
BROKER_URL = 'redis://120.132.54.90:6379/0',
对应你自己的MySQL-server服务器IP
CELERY_RESULT_BACKEND = 'db+mysql://celery:celery1@3Wscan@42.62.52.62:443/wscan',
配置完毕后,就可以部署多台客户端进行分布式任务执行了
## CentOS 客户端(建议大规模部署)
# 安装 git & 下载 thorns_project
$ sudo yum -y install git
$ cd /home/
$ git clone https://github.com/ring04h/thorns.git
# 安装 pip
$ wget https://pypi.python.org/packages/source/p/pip/pip-6.0.8.tar.gz
$ tar zvxf pip-6.0.8.tar.gz
$ cd pip-6.0.8
$ sudo python setup.py install
# 安装 MySQL-python
$ sudo yum -y install python-devel mysql-devel subversion-devel
$ sudo pip install MySQL-python SQLAlchemy
# 安装 nmap
# 32位系统
$ sudo rpm -vhU https://nmap.org/dist/nmap-6.47-1.i386.rpm
# 64位系统
$ sudo rpm -vhU https://nmap.org/dist/nmap-6.47-1.x86_64.rpm
# 安装 Celery
$ sudo pip install -U celery[redis]
# 安装 Supervisord
$ sudo pip install supervisor
$ sudo cp /home/thorns/src/supervisord_client.conf /etc/supervisord.conf
/* 修改 /etc/supervisord.conf 140行 修改programe为你想定义的worker名称 [program:worker-ringzero] */
$ sudo vim /etc/supervisord.conf
/* 启动 supervisord */
$ supervisord -c /etc/supervisord.conf
http://127.0.0.1:9001/ 可以在线守护管理thorns的进程,实现远程重启
# 修改tasks.py内的芹菜配置(分布式任务关键配置项)
对应你自己的redis-server服务器IP
BROKER_URL = 'redis://120.132.54.90:6379/0',
对应你自己的MySQL-server服务器IP
CELERY_RESULT_BACKEND = 'db+mysql://celery:celery1@3Wscan@42.62.52.62:443/wscan',
# 环境搭建完毕,这时候访问thorns project的控制台,就会发现worker客户端已经出现在那里
演示地址:http://thorns.wuyun.org:8080/
你的请访问:http://youip:8080/
使用说明(可客户端发起任务也可http api发起任务)
-----------------------------------
#### 命令行调用
在你的任意一台worker客户端,或者thorns服务端
$ cd /home/thorns/
$ python run.py 42.62.52.1-42.62.62.254 188
$ python run.py 42.62.52.1-254 189
均可以向redis压入nmap扫描任务,worker客户端的分布式集群会自动分发任务执行,并存储到后台数据库
记得修改wyportmap.py里面的扫描结果,存到你自己的数据库
reinhard-mbp:thorns reinhard$ python run.py 42.62.52.1-254 189
--------------------------------------------------
* push 42.62.52.1 to Redis
* AsyncResult:23147d02-294d-41e5-84e5-5e1b15e72fc4
--------------------------------------------------
* push 42.62.52.2 to Redis
* AsyncResult:542984a4-4434-475f-9a62-bfc81206ea57
--------------------------------------------------
* push 42.62.52.3 to Redis
* AsyncResult:7d005661-d719-41ef-babc-4c853b2c49cc
--------------------------------------------------
* push 42.62.52.4 to Redis
* AsyncResult:ddcf9486-09d9-4dd2-9bb4-2618e6a161b8
--------------------------------------------------
wyportmap相关帮助: https://github.com/ring04h/wyportmap
#### HTTP API 远程调用
重启 worker 线程池:
$ curl -X POST http://thorns.wuyun.org:8080/api/worker/pool/restart/myworker
远程调用HTTP API启动一个nmap扫描任务:
$ curl -X POST -d '{"args":["42.62.52.62",2222]}' http://thorns.wuyun.org:8080/api/task/send-task/tasks.nmap_dispath
强制结束一个正在执行的任务:
$ curl -X POST -d 'terminate=True' http://thorns.wuyun.org:8088/api/task/revoke/a9361c1b-fd1d-4f48-9be2-8656a57e906b
================================================
FILE: libnmap/__init__.py
================================================
# -*- coding: utf-8 -*-
__author__ = 'Ronald Bister, Mike Boutillier'
__credits__ = ['Ronald Bister', 'Mike Boutillier']
__maintainer__ = 'Ronald Bister'
__email__ = 'mini.pelle@gmail.com'
__license__ = 'CC-BY'
__version__ = '0.6.1'
================================================
FILE: libnmap/diff.py
================================================
# -*- coding: utf-8 -*-
class DictDiffer(object):
"""
Calculate the difference between two dictionaries as:
(1) items added
(2) items removed
(3) keys same in both but changed values
(4) keys same in both and unchanged values
"""
def __init__(self, current_dict, past_dict):
self.current_dict = current_dict
self.past_dict = past_dict
self.set_current = set(current_dict.keys())
self.set_past = set(past_dict.keys())
self.intersect = self.set_current.intersection(self.set_past)
def added(self):
return self.set_current - self.intersect
def removed(self):
return self.set_past - self.intersect
def changed(self):
return (set(o for o in self.intersect
if self.past_dict[o] != self.current_dict[o]))
def unchanged(self):
return (set(o for o in self.intersect
if self.past_dict[o] == self.current_dict[o]))
class NmapDiff(DictDiffer):
"""
NmapDiff compares two objects of same type to enable the user to check:
- what has changed
- what has been added
- what has been removed
- what was kept unchanged
NmapDiff inherit from DictDiffer which makes the actual comparaison.
The different methods from DictDiffer used by NmapDiff are the
following:
- NmapDiff.changed()
- NmapDiff.added()
- NmapDiff.removed()
- NmapDiff.unchanged()
Each of the returns a python set() of key which have changed in the
compared objects. To check the different keys that could be returned,
refer to the get_dict() method of the objects you which to
compare (i.e: libnmap.objects.NmapHost, NmapService,...).
"""
def __init__(self, nmap_obj1, nmap_obj2):
"""
Constructor of NmapDiff:
- Checks if the two objects are of the same class
- Checks if the objects are "comparable" via a call to id() (dirty)
- Inherits from DictDiffer and
"""
if(nmap_obj1.__class__ != nmap_obj2.__class__ or
nmap_obj1.id != nmap_obj2.id):
raise NmapDiffException("Comparing objects with non-matching id")
self.object1 = nmap_obj1.get_dict()
self.object2 = nmap_obj2.get_dict()
DictDiffer.__init__(self, self.object1, self.object2)
def __repr__(self):
return ("added: [{0}] -- changed: [{1}] -- "
"unchanged: [{2}] -- removed [{3}]".format(self.added(),
self.changed(),
self.unchanged(),
self.removed()))
class NmapDiffException(Exception):
def __init__(self, msg):
self.msg = msg
================================================
FILE: libnmap/objects/__init__.py
================================================
# -*- coding: utf-8 -*-
from libnmap.objects.report import NmapReport
from libnmap.objects.host import NmapHost
from libnmap.objects.service import NmapService
__all__ = ['NmapReport', 'NmapHost', 'NmapService']
================================================
FILE: libnmap/objects/cpe.py
================================================
# -*- coding: utf-8 -*-
class CPE(object):
"""
CPE class offers an API for basic CPE objects.
These objects could be found in NmapService or in <os> tag
within NmapHost.
:todo: interpret CPE string and provide appropriate API
"""
def __init__(self, cpestring):
self._cpestring = cpestring
self.cpedict = {}
zk = ['cpe', 'part', 'vendor', 'product', 'version',
'update', 'edition', 'language']
self._cpedict = dict((k, '') for k in zk)
splitup = cpestring.split(':')
self._cpedict.update(dict(zip(zk, splitup)))
@property
def cpestring(self):
"""
Accessor for the full CPE string.
"""
return self._cpestring
def __repr__(self):
return self._cpestring
def get_part(self):
"""
Returns the cpe part (/o, /h, /a)
"""
return self._cpedict['part']
def get_vendor(self):
"""
Returns the vendor name
"""
return self._cpedict['vendor']
def get_product(self):
"""
Returns the product name
"""
return self._cpedict['product']
def get_version(self):
"""
Returns the version of the cpe
"""
return self._cpedict['version']
def get_update(self):
"""
Returns the update version
"""
return self._cpedict['update']
def get_edition(self):
"""
Returns the cpe edition
"""
return self._cpedict['edition']
def get_language(self):
"""
Returns the cpe language
"""
return self._cpedict['language']
def is_application(self):
"""
Returns True if cpe describes an application
"""
return (self.get_part() == '/a')
def is_hardware(self):
"""
Returns True if cpe describes a hardware
"""
return (self.get_part() == '/h')
def is_operating_system(self):
"""
Returns True if cpe describes an operating system
"""
return (self.get_part() == '/o')
================================================
FILE: libnmap/objects/host.py
================================================
# -*- coding: utf-8 -*-
from libnmap.diff import NmapDiff
from libnmap.objects.os import NmapOSFingerprint
class NmapHost(object):
"""
NmapHost is a class representing a host object of NmapReport
"""
def __init__(self, starttime='', endtime='', address=None, status=None,
hostnames=None, services=None, extras=None):
"""
NmapHost constructor
:param starttime: unix timestamp of when the scan against
that host started
:type starttime: string
:param endtime: unix timestamp of when the scan against
that host ended
:type endtime: string
:param address: dict ie :{'addr': '127.0.0.1', 'addrtype': 'ipv4'}
:param status: dict ie:{'reason': 'localhost-response',
'state': 'up'}
:return: NmapHost:
"""
self._starttime = starttime
self._endtime = endtime
self._hostnames = hostnames if hostnames is not None else []
self._status = status if status is not None else {}
self._services = services if services is not None else []
self._extras = extras if extras is not None else {}
self._osfingerprinted = False
self.os = None
if 'os' in self._extras:
self.os = NmapOSFingerprint(self._extras['os'])
self._osfingerprinted = True
else:
self.os = NmapOSFingerprint({})
self._ipv4_addr = None
self._ipv6_addr = None
self._mac_addr = None
self._vendor = None
for addr in address:
if addr['addrtype'] == "ipv4":
self._ipv4_addr = addr['addr']
elif addr['addrtype'] == 'ipv6':
self._ipv6_addr = addr['addr']
elif addr['addrtype'] == 'mac':
self._mac_addr = addr['addr']
if 'vendor' in addr:
self._vendor = addr['vendor']
self._main_address = self._ipv4_addr or self._ipv6_addr or ''
self._address = address
def __eq__(self, other):
"""
Compare eq NmapHost based on :
- hostnames
- address
- if an associated services has changed
:return: boolean
"""
rval = False
if(self.__class__ == other.__class__ and self.id == other.id):
rval = (self.changed(other) == 0)
return rval
def __ne__(self, other):
"""
Compare ne NmapHost based on:
- hostnames
- address
- if an associated services has changed
:return: boolean
"""
rval = True
if(self.__class__ == other.__class__ and self.id == other.id):
rval = (self.changed(other) > 0)
return rval
def __repr__(self):
"""
String representing the object
:return: string
"""
return "{0}: [{1} ({2}) - {3}]".format(self.__class__.__name__,
self.address,
" ".join(self._hostnames),
self.status)
def __hash__(self):
"""
Hash is needed to be able to use our object in sets
:return: hash
"""
return (hash(self.status) ^ hash(self.address) ^
hash(frozenset(self._services)) ^
hash(frozenset(" ".join(self._hostnames))))
def changed(self, other):
"""
return the number of attribute who have changed
:param other: NmapHost object to compare
:return int
"""
return len(self.diff(other).changed())
def save(self, backend):
if backend is not None:
_id = backend.insert(self)
else:
raise RuntimeError
return _id
@property
def starttime(self):
"""
Accessor for the unix timestamp of when the scan was started
:return: string
"""
return self._starttime
@property
def endtime(self):
"""
Accessor for the unix timestamp of when the scan ended
:return: string
"""
return self._endtime
@property
def address(self):
"""
Accessor for the IP address of the scanned host
:return: IP address as a string
"""
return self._main_address
@address.setter
def address(self, addrdict):
"""
Setter for the address dictionnary.
:param addrdict: valid dict is {'addr': '1.1.1.1',
'addrtype': 'ipv4'}
"""
if addrdict['addrtype'] == 'ipv4':
self._ipv4_addr = addrdict['addr']
elif addrdict['addrtype'] == 'ipv6':
self._ipv6_addr = addrdict['addr']
elif addrdict['addrtype'] == 'mac':
self._mac_addr = addrdict['addr']
if 'vendor' in addrdict:
self._vendor = addrdict['vendor']
self._main_address = self._ipv4_addr or self._ipv6_addr or ''
self._address = addrdict
@property
def ipv4(self):
"""
Accessor for the IPv4 address of the scanned host
:return: IPv4 address as a string
"""
return self._ipv4_addr or ''
@property
def mac(self):
"""
Accessor for the MAC address of the scanned host
:return: MAC address as a string
"""
return self._mac_addr or ''
@property
def vendor(self):
"""
Accessor for the vendor attribute of the scanned host
:return: string (vendor) of empty string if no vendor defined
"""
return self._vendor or ''
@property
def ipv6(self):
"""
Accessor for the IPv6 address of the scanned host
:return: IPv6 address as a string
"""
return self._ipv6_addr or ''
@property
def status(self):
"""
Accessor for the host's status (up, down, unknown...)
:return: string
"""
return self._status['state']
@status.setter
def status(self, statusdict):
"""
Setter for the status dictionnary.
:param statusdict: valid dict is {"state": "open",
"reason": "syn-ack",
"reason_ttl": "0"}
'state' is the only mandatory key.
"""
self._status = statusdict
def is_up(self):
"""
method to determine if host is up or not
:return: bool
"""
rval = False
if self.status == 'up':
rval = True
return rval
@property
def hostnames(self):
"""
Accessor returning the list of hostnames (array of strings).
:return: array of string
"""
return self._hostnames
@property
def services(self):
"""
Accessor for the array of scanned services for that host.
An array of NmapService objects is returned.
:return: array of NmapService
"""
return self._services
def get_ports(self):
"""
Retrieve a list of the port used by each service of the NmapHost
:return: list: of tuples (port,'proto') ie:[(22,'tcp'),(25, 'tcp')]
"""
return [(p.port, p.protocol) for p in self._services]
def get_open_ports(self):
"""
Same as get_ports() but only for open ports
:return: list: of tuples (port,'proto') ie:[(22,'tcp'),(25, 'tcp')]
"""
return ([(p.port, p.protocol)
for p in self._services if p.state == 'open'])
def get_service(self, portno, protocol='tcp'):
"""
:param portno: int the portnumber
:param protocol='tcp': string ('tcp','udp')
:return: NmapService or None
"""
plist = [p for p in self._services if
p.port == portno and p.protocol == protocol]
if len(plist) > 1:
raise Exception("Duplicate services found in NmapHost object")
return plist.pop() if len(plist) else None
def get_service_byid(self, service_id):
"""
Returns a NmapService by providing its id.
The id of a nmap service is a python tupl made of (protocol, port)
"""
rval = None
for _tmpservice in self._services:
if _tmpservice.id == service_id:
rval = _tmpservice
return rval
def os_class_probabilities(self):
"""
Returns an array of possible OS class detected during
the OS fingerprinting.
:return: Array of NmapOSClass objects
"""
rval = []
if self.os is not None:
rval = self.os.osclasses
return rval
def os_match_probabilities(self):
"""
Returns an array of possible OS match detected during
the OS fingerprinting
:return: array of NmapOSMatches objects
"""
rval = []
if self.os is not None:
rval = self.os.osmatches
return rval
@property
def os_fingerprinted(self):
"""
Specify if the host has OS fingerprint data available
:return: Boolean
"""
return self._osfingerprinted
@property
def os_fingerprint(self):
"""
Returns the fingerprint of the scanned system.
:return: string
"""
rval = ''
if self.os is not None:
rval = "\n".join(self.os.fingerprints)
return rval
def os_ports_used(self):
"""
Returns an array of the ports used for OS fingerprinting
:return: array of ports used: [{'portid': '22',
'proto': 'tcp',
'state': 'open'},]
"""
rval = []
try:
rval = self._extras['os']['ports_used']
except (KeyError, TypeError):
pass
return rval
@property
def tcpsequence(self):
"""
Returns the difficulty to determine remotely predict
the tcp sequencing.
return: string
"""
rval = ''
try:
rval = self._extras['tcpsequence']['difficulty']
except (KeyError, TypeError):
pass
return rval
@property
def ipsequence(self):
"""
Return the class of ip sequence of the remote hosts.
:return: string
"""
rval = ''
try:
rval = self._extras['ipidsequence']['class']
except (KeyError, TypeError):
pass
return rval
@property
def uptime(self):
"""
uptime of the remote host (if nmap was able to determine it)
:return: string (in seconds)
"""
rval = 0
try:
rval = int(self._extras['uptime']['seconds'])
except (KeyError, TypeError):
pass
return rval
@property
def lastboot(self):
"""
Since when the host was booted.
:return: string
"""
rval = ''
try:
rval = self._extras['uptime']['lastboot']
except (KeyError, TypeError):
pass
return rval
@property
def distance(self):
"""
Number of hops to host
:return: int
"""
rval = 0
try:
rval = int(self._extras['distance']['value'])
except (KeyError, TypeError):
pass
return rval
@property
def scripts_results(self):
"""
Scripts results specific to the scanned host
:return: array of <script> dictionary
"""
rval = {}
try:
rval = self._extras['hostscript']
except (KeyError, TypeError):
pass
return rval
@property
def id(self):
"""
id of the host. Used for diff()ing NmapObjects
:return: string
"""
return self.address
@property
def extraports_state(self):
"""
dictionnary containing state and amount of extra ports scanned
for which a common state, usually, closed was discovered.
:return: dict with keys 'state' and 'count' or None
"""
_xtrports = self._extras.get('extraports', None)
if _xtrports is None:
return None
return {'state': _xtrports['state'], 'count': _xtrports['count']}
@property
def extraports_reasons(self):
"""
dictionnary containing reasons why extra ports scanned
for which a common state, usually, closed was discovered.
:return: array of dict containing keys 'state' and 'count' or None
"""
r = self._extras.get('extraports', {})
return r.get('reasons', None)
def get_dict(self):
"""
Return a dict representation of the object.
This is needed by NmapDiff to allow comparaison
:return dict
"""
d = dict([("{0}::{1}".format(s.__class__.__name__, str(s.id)),
hash(s))
for s in self.services])
d.update({'address': self.address, 'status': self.status,
'hostnames': " ".join(self._hostnames)})
return d
def diff(self, other):
"""
Calls NmapDiff to check the difference between self and
another NmapHost object.
Will return a NmapDiff object.
This objects return python set() of keys describing the elements
which have changed, were added, removed or kept unchanged.
:param other: NmapHost to diff with
:return: NmapDiff object
"""
return NmapDiff(self, other)
================================================
FILE: libnmap/objects/os.py
================================================
# -*- coding: utf-8 -*-
import warnings
from libnmap.objects.cpe import CPE
class OSFPPortUsed(object):
"""
Port used class: this enables the user of NmapOSFingerprint class
to have a common and clear interface to access portused data which
were collected and used during os fingerprint scan
"""
def __init__(self, port_used_dict):
try:
self._state = port_used_dict['state']
self._proto = port_used_dict['proto']
self._portid = port_used_dict['portid']
except KeyError:
raise Exception("Cannot create OSFPPortUsed: missing required key")
@property
def state(self):
"""
Accessor for the portused state (closed, open,...)
"""
return self._state
@property
def proto(self):
"""
Accessor for the portused protocol (tcp, udp,...)
"""
return self._proto
@property
def portid(self):
"""
Accessor for the referenced port number used
"""
return self._portid
class NmapOSMatch(object):
"""
NmapOSMatch is an internal class used for offering results
from an nmap os fingerprint. This common interfaces makes
a compatibility between old nmap xml (<1.04) and new nmap
xml versions (used in nmapv6 for instance).
In previous xml version, osclass tags from nmap fingerprints
were not directly mapped to a osmatch. In new xml version,
osclass could be embedded in osmatch tag.
The approach to solve this is to create a common class
which will, for older xml version, match based on the accuracy
osclass to an osmatch. If no match, an osmatch will be made up
from a concat of os class attributes: vendor and osfamily.
Unmatched osclass will have a line attribute of -1.
More info, see issue #26 or http://seclists.org/nmap-dev/2012/q2/252
"""
def __init__(self, osmatch_dict):
_osmatch_dict = osmatch_dict['osmatch']
if('name' not in _osmatch_dict or
'line' not in _osmatch_dict or
'accuracy' not in _osmatch_dict):
raise Exception("Cannot create NmapOSClass: missing required key")
self._name = _osmatch_dict['name']
self._line = _osmatch_dict['line']
self._accuracy = _osmatch_dict['accuracy']
# create osclass list
self._osclasses = []
try:
for _osclass in osmatch_dict['osclasses']:
try:
_osclassobj = NmapOSClass(_osclass)
except:
raise Exception("Could not create NmapOSClass object")
self._osclasses.append(_osclassobj)
except KeyError:
pass
def add_osclass(self, osclass_obj):
"""
Add a NmapOSClass object to the OSMatch object. This method is
useful to implement compatibility with older versions of NMAP
by providing a common interface to access os fingerprint data.
"""
self._osclasses.append(osclass_obj)
@property
def osclasses(self):
"""
Accessor for all NmapOSClass objects matching with this OS Match
"""
return self._osclasses
@property
def name(self):
"""
Accessor for name attribute (e.g.: Linux 2.4.26 (Slackware 10.0.0))
"""
return self._name
@property
def line(self):
"""
Accessor for line attribute as integer. value equals -1 if this
osmatch holds orphans NmapOSClass objects. This could happen with
older version of nmap xml engine (<1.04 (e.g: nmapv6)).
:return: int
"""
return int(self._line)
@property
def accuracy(self):
"""
Accessor for accuracy
:return: int
"""
return int(self._accuracy)
def get_cpe(self):
"""
This method return a list of cpe stings and not CPE objects as
the NmapOSClass.cpelist property. This method is a helper to
simplify data management.
For more advanced handling of CPE data, use NmapOSClass.cpelist
and use the methods from CPE class
"""
_cpelist = []
for osc in self.osclasses:
for cpe in osc.cpelist:
_cpelist.append(cpe.cpestring)
return _cpelist
def __repr__(self):
rval = "{0}: {1}".format(self.name, self.accuracy)
for _osclass in self._osclasses:
rval += "\r\n |__ os class: {0}".format(str(_osclass))
return rval
class NmapOSClass(object):
"""
NmapOSClass offers an unified API to access data from analysed
osclass tag. As implemented in libnmap and newer version of nmap,
osclass objects will always be embedded in a NmapOSMatch.
Unmatched NmapOSClass will be stored in "dummy" NmapOSMatch objects
which will have the particularity of have a line attribute of -1.
On top of this, NmapOSClass will have optional CPE objects
embedded.
"""
def __init__(self, osclass_dict):
_osclass = osclass_dict['osclass']
if('vendor' not in _osclass or
'osfamily' not in _osclass or
'accuracy' not in _osclass):
raise Exception("Wrong osclass structure: missing required key")
self._vendor = _osclass['vendor']
self._osfamily = _osclass['osfamily']
self._accuracy = _osclass['accuracy']
self._osgen = ''
self._type = ''
# optional data
if 'osgen' in _osclass:
self._osgen = _osclass['osgen']
if 'type' in _osclass:
self._type = _osclass['type']
self._cpelist = []
for _cpe in osclass_dict['cpe']:
self._cpelist.append(CPE(_cpe))
@property
def cpelist(self):
"""
Returns a list of CPE Objects matching with this os class
:return: list of CPE objects
:rtype: Array
"""
return self._cpelist
@property
def vendor(self):
"""
Accessor for vendor information (Microsoft, Linux,...)
:return: string
"""
return self._vendor
@property
def osfamily(self):
"""
Accessor for OS family information (Windows, Linux,...)
:return: string
"""
return self._osfamily
@property
def accuracy(self):
"""
Accessor for OS class detection accuracy (int)
:return: int
"""
return int(self._accuracy)
@property
def osgen(self):
"""
Accessor for OS class generation (7, 8, 2.4.X,...).
:return: string
"""
return self._osgen
@property
def type(self):
"""
Accessor for OS class type (general purpose,...)
:return: string
"""
return self._type
@property
def description(self):
"""
Accessor helper which returns a concataned string of
the valuable attributes from NmapOSClass object
:return: string
"""
rval = "{0}: {1}, {2}".format(self.type, self.vendor, self.osfamily)
if len(self.osgen):
rval += "({0})".format(self.osgen)
return rval
def __repr__(self):
rval = "{0}: {1}, {2}".format(self.type, self.vendor, self.osfamily)
if len(self.osgen):
rval += "({0})".format(self.osgen)
for _cpe in self._cpelist:
rval += "\r\n |__ {0}".format(str(_cpe))
return rval
class NmapOSFingerprint(object):
"""
NmapOSFingerprint is a easier API for using os fingerprinting.
Data for OS fingerprint (<os> tag) is instanciated from
a NmapOSFingerprint which is accessible in NmapHost via NmapHost.os
"""
def __init__(self, osfp_data):
self.__osmatches = []
self.__ports_used = []
self.__fingerprints = []
if 'osmatches' in osfp_data:
for _osmatch in osfp_data['osmatches']:
_osmatch_obj = NmapOSMatch(_osmatch)
self.__osmatches.append(_osmatch_obj)
if 'osclasses' in osfp_data:
for _osclass in osfp_data['osclasses']:
_osclass_obj = NmapOSClass(_osclass)
_osmatched = self.get_osmatch(_osclass_obj)
if _osmatched is not None:
_osmatched.add_osclass(_osclass_obj)
else:
self._add_dummy_osmatch(_osclass_obj)
if 'osfingerprints' in osfp_data:
for _osfp in osfp_data['osfingerprints']:
if 'fingerprint' in _osfp:
self.__fingerprints.append(_osfp['fingerprint'])
if 'ports_used' in osfp_data:
for _pused_dict in osfp_data['ports_used']:
_pused = OSFPPortUsed(_pused_dict)
self.__ports_used.append(_pused)
def get_osmatch(self, osclass_obj):
"""
This function enables NmapOSFingerprint to determine if an
NmapOSClass object could be attached to an existing NmapOSMatch
object in order to respect the common interface for
the nmap xml version < 1.04 and >= 1.04
This method will return an NmapOSMatch object matching with
the NmapOSClass provided in parameter
(match is performed based on accuracy)
:return: NmapOSMatch object
"""
rval = None
for _osmatch in self.__osmatches:
if _osmatch.accuracy == osclass_obj.accuracy:
rval = _osmatch
break # sorry
return rval
def _add_dummy_osmatch(self, osclass_obj):
"""
This functions creates a dummy NmapOSMatch object in order to
encapsulate an NmapOSClass object which was not matched with an
existing NmapOSMatch object
"""
_dname = "{0}:{1}:{2}".format(osclass_obj.type,
osclass_obj.vendor,
osclass_obj.osfamily)
_dummy_dict = {'osmatch': {'name': _dname,
'accuracy': osclass_obj.accuracy,
'line': -1},
'osclasses': []}
_dummy_osmatch = NmapOSMatch(_dummy_dict)
self.__osmatches.append(_dummy_osmatch)
@property
def osmatches(self, min_accuracy=0):
_osmatches = []
for _osmatch in self.__osmatches:
if _osmatch.accuracy >= min_accuracy:
_osmatches.append(_osmatch)
return _osmatches
@property
def fingerprint(self):
return "\r\n".join(self.__fingerprints)
@property
def fingerprints(self):
return self.__fingerprints
@property
def ports_used(self):
"""
Return an array of OSFPPortUsed object with the ports used to
perform the os fingerprint. This dict might contain another dict
embedded containing the ports_reason values.
"""
return self.__ports_used
def osmatch(self, min_accuracy=90):
warnings.warn("NmapOSFingerprint.osmatch is deprecated: "
"use NmapOSFingerprint.osmatches", DeprecationWarning)
os_array = []
for _osmatch in self.__osmatches:
if _osmatch.accuracy >= min_accuracy:
os_array.append(_osmatch.name)
return os_array
def osclass(self, min_accuracy=90):
warnings.warn("NmapOSFingerprint.osclass() is deprecated: "
"use NmapOSFingerprint.osclasses() if applicable",
DeprecationWarning)
os_array = []
for osmatch_entry in self.osmatches():
if osmatch_entry.accuracy >= min_accuracy:
for oclass in osmatch_entry.osclasses:
_ftstr = "type:{0}|vendor:{1}|osfamily{2}".format(
oclass.type,
oclass.vendor,
oclass.osfamily)
os_array.append(_ftstr)
return os_array
def os_cpelist(self):
cpelist = []
for _osmatch in self.osmatches:
for oclass in _osmatch.osclasses:
cpelist.extend(oclass.cpelist)
return cpelist
def __repr__(self):
rval = ""
for _osmatch in self.osmatches:
rval += "\r\n{0}".format(_osmatch)
rval += "Fingerprints: ".format(self.fingerprint)
return rval
================================================
FILE: libnmap/objects/report.py
================================================
# -*- coding: utf-8 -*-
from libnmap.diff import NmapDiff
class NmapReport(object):
"""
NmapReport is the usual interface for the end user to
read scans output.
A NmapReport as the following structure:
- Scan headers data
- A list of scanned hosts (NmapReport.hosts)
- Scan footer data
It is to note that each NmapHost comprised in NmapReport.hosts array
contains also a list of scanned services (NmapService object).
This means that if NmapParser.parse*() is the input interface for the
end user of the lib. NmapReport is certainly the output interface for
the end user of the lib.
"""
def __init__(self, raw_data=None):
"""
Constructor for NmapReport object.
This is usually called by the NmapParser module.
"""
self._nmaprun = {}
self._scaninfo = {}
self._hosts = []
self._runstats = {}
if raw_data is not None:
self.__set_raw_data(raw_data)
def save(self, backend):
"""
This method gets a NmapBackendPlugin representing the backend.
:param backend: libnmap.plugins.PluginBackend object.
Object created by BackendPluginFactory and enabling nmap reports
to be saved/stored in any type of backend implemented in plugins.
The primary key of the stored object is returned.
:return: str
"""
if backend is not None:
_id = backend.insert(self)
else:
raise RuntimeError
return _id
def diff(self, other):
"""
Calls NmapDiff to check the difference between self and
another NmapReport object.
Will return a NmapDiff object.
:return: NmapDiff object
:todo: remove is_consistent approach, diff() should be generic.
"""
if self.is_consistent() and other.is_consistent():
_rdiff = NmapDiff(self, other)
else:
_rdiff = set()
return _rdiff
@property
def started(self):
"""
Accessor returning a unix timestamp of when the scan was started.
:return: integer
"""
rval = -1
try:
s_start = self._nmaprun['start']
rval = int(s_start)
except(KeyError, TypeError, ValueError):
pass
return rval
@property
def commandline(self):
"""
Accessor returning the full nmap command line fired.
:return: string
"""
return self._nmaprun['args']
@property
def version(self):
"""
Accessor returning the version of the
nmap binary used to perform the scan.
:return: string
"""
return self._nmaprun['version']
@property
def scan_type(self):
"""
Accessor returning a string which identifies what type of scan
was launched (syn, ack, tcp,...).
:return: string
"""
return self._scaninfo['type']
@property
def hosts(self):
"""
Accessor returning an array of scanned hosts.
Scanned hosts are NmapHost objects.
:return: array of NmapHost
"""
return self._hosts
def get_host_byid(self, host_id):
"""
Gets a NmapHost object directly from the host array
by looking it up by id.
:param ip_addr: ip address of the host to lookup
:type ip_addr: string
:return: NmapHost
"""
rval = None
for _rhost in self._hosts:
if _rhost.address == host_id:
rval = _rhost
return rval
@property
def endtime(self):
"""
Accessor returning a unix timestamp of when the scan ended.
:return: integer
"""
rval = -1
try:
rval = int(self._runstats['finished']['time'])
except(KeyError, TypeError, ValueError):
pass
return rval
@property
def endtimestr(self):
"""
Accessor returning a human readable time string
of when the scan ended.
:return: string
"""
rval = ''
try:
rval = self._runstats['finished']['timestr']
except(KeyError, TypeError, ValueError):
pass
return rval
@property
def summary(self):
"""
Accessor returning a string describing and
summarizing the scan.
:return: string
"""
rval = ''
try:
rval = self._runstats['finished']['summary']
except(KeyError, TypeError):
pass
if len(rval) == 0:
rval = ("Nmap ended at {0} ; {1} IP addresses ({2} hosts up)"
" scanned in {3} seconds".format(self.endtimestr,
self.hosts_total,
self.hosts_up,
self.elapsed))
return rval
@property
def elapsed(self):
"""
Accessor returning the number of seconds the scan took
:return: float (0 >= or -1)
"""
rval = -1
try:
s_elapsed = self._runstats['finished']['elapsed']
rval = float(s_elapsed)
except (KeyError, TypeError, ValueError):
rval = -1
return rval
@property
def hosts_up(self):
"""
Accessor returning the numer of host detected
as 'up' during the scan.
:return: integer (0 >= or -1)
"""
rval = -1
try:
s_up = self._runstats['hosts']['up']
rval = int(s_up)
except (KeyError, TypeError, ValueError):
rval = -1
return rval
@property
def hosts_down(self):
"""
Accessor returning the numer of host detected
as 'down' during the scan.
:return: integer (0 >= or -1)
"""
rval = -1
try:
s_down = self._runstats['hosts']['down']
rval = int(s_down)
except (KeyError, TypeError, ValueError):
rval = -1
return rval
@property
def hosts_total(self):
"""
Accessor returning the number of hosts scanned in total.
:return: integer (0 >= or -1)
"""
rval = -1
try:
s_total = self._runstats['hosts']['total']
rval = int(s_total)
except (KeyError, TypeError, ValueError):
rval = -1
return rval
def get_raw_data(self):
"""
Returns a dict representing the NmapReport object.
:return: dict
:todo: deprecate. get rid of this uglyness.
"""
raw_data = {'_nmaprun': self._nmaprun,
'_scaninfo': self._scaninfo,
'_hosts': self._hosts,
'_runstats': self._runstats}
return raw_data
def __set_raw_data(self, raw_data):
self._nmaprun = raw_data['_nmaprun']
self._scaninfo = raw_data['_scaninfo']
self._hosts = raw_data['_hosts']
self._runstats = raw_data['_runstats']
def is_consistent(self):
"""
Checks if the report is consistent and can be diffed().
This needs to be rewritten and removed: diff() should be generic.
:return: boolean
"""
rval = False
rdata = self.get_raw_data()
_consistent_keys = ['_nmaprun', '_scaninfo', '_hosts', '_runstats']
if(set(_consistent_keys) == set(rdata.keys()) and
len([dky for dky in rdata.keys() if rdata[dky] is not None]) == 4):
rval = True
return rval
def get_dict(self):
"""
Return a python dict representation of the NmapReport object.
This is used to diff() NmapReport objects via NmapDiff.
:return: dict
"""
rdict = dict([("{0}::{1}".format(_host.__class__.__name__,
str(_host.id)),
hash(_host)) for _host in self.hosts])
rdict.update({'commandline': self.commandline,
'version': self.version,
'scan_type': self.scan_type,
'elapsed': self.elapsed,
'hosts_up': self.hosts_up,
'hosts_down': self.hosts_down,
'hosts_total': self.hosts_total})
return rdict
@property
def id(self):
"""
Dummy id() defined for reports.
"""
return hash(1)
def __eq__(self, other):
"""
Compare eq NmapReport based on :
- create a diff obj and check the result
report are equal if added&changed&removed are empty
:return: boolean
"""
rval = False
if(self.__class__ == other.__class__ and self.id == other.id):
diffobj = self.diff(other)
rval = (len(diffobj.changed()) == 0 and
len(diffobj.added()) == 0 and
len(diffobj.removed()) == 0
)
return rval
def __ne__(self, other):
"""
Compare ne NmapReport based on:
- create a diff obj and check the result
report are ne if added|changed|removed are not empty
:return: boolean
"""
rval = True
if(self.__class__ == other.__class__ and self.id == other.id):
diffobj = self.diff(other)
rval = (len(diffobj.changed()) != 0 or
len(diffobj.added()) != 0 or
len(diffobj.removed()) != 0
)
return rval
def __repr__(self):
return "{0}: started at {1} hosts up {2}/{3}".format(
self.__class__.__name__,
self.started,
self.hosts_up,
self.hosts_total)
================================================
FILE: libnmap/objects/service.py
================================================
# -*- coding: utf-8 -*-
from libnmap.diff import NmapDiff
from libnmap.objects.os import CPE
class NmapService(object):
"""
NmapService represents a nmap scanned service. Its id() is comprised
of the protocol and the port.
Depending on the scanning options, some additional details might be
available or not. Like banner or extra datas from NSE (nmap scripts).
"""
def __init__(self, portid, protocol='tcp', state=None,
service=None, owner=None, service_extras=None):
"""
Constructor
:param portid: port number
:type portid: string
:param protocol: protocol of port scanned (tcp, udp)
:type protocol: string
:param state: python dict describing the service status
:type state: python dict
:param service: python dict describing the service name and banner
:type service: python dict
:param service_extras: additional info about the tested service
like scripts' data
"""
try:
self._portid = int(portid or -1)
except (ValueError, TypeError):
raise
if self._portid < 0 or self._portid > 65535:
raise ValueError
self._protocol = protocol
# self._taskid = taskid
# self._address = address
self._state = state if state is not None else {}
self._service = service if service is not None else {}
self._cpelist = []
if 'cpelist' in self._service:
for _cpe in self._service['cpelist']:
_cpeobj = CPE(_cpe)
self._cpelist.append(_cpeobj)
self._owner = ''
if owner is not None and 'name' in owner:
self._owner = owner['name']
self._reason = ''
self._reason_ip = ''
self._reason_ttl = ''
self._servicefp = ''
self._tunnel = ''
if 'reason' in self._state:
self._reason = self._state['reason']
if 'reason_ttl' in self._state:
self._reason_ttl = self._state['reason_ttl']
if 'reason_ip' in self._state:
self._reason_ip = self._state['reason_ip']
if 'servicefp' in self._service:
self._servicefp = self._service['servicefp']
if 'tunnel' in self._service:
self._tunnel = self._service['tunnel']
self._service_extras = []
if service_extras is not None:
self._service_extras = service_extras
def __eq__(self, other):
"""
Compares two NmapService objects to see if they are the same or
if one of them changed.
:param other: NmapService
:return: boolean
"""
rval = False
if(self.__class__ == other.__class__ and self.id == other.id):
rval = (self.changed(other) == 0)
return rval
def __ne__(self, other):
"""
Compares two NmapService objects to see if they are different
if one of them changed.
:param other: NmapService
:return: boolean
"""
rval = True
if(self.__class__ == other.__class__ and self.id == other.id):
rval = (self.changed(other) > 0)
return rval
def __repr__(self):
return "{0}: [{1} {2}/{3} {4} ({5})]".format(self.__class__.__name__,
self.state,
str(self.port),
self.protocol,
self.service,
self.banner)
def __hash__(self):
return (hash(self.port) ^ hash(self.protocol) ^ hash(self.state) ^
hash(self.reason) ^ hash(self.service) ^ hash(self.banner))
def changed(self, other):
"""
Checks if a NmapService is different from another.
:param other: NmapService
:return: boolean
"""
return len(self.diff(other).changed())
def save(self, backend):
if backend is not None:
_id = backend.insert(self)
else:
raise RuntimeError
return _id
@property
def port(self):
"""
Accessor for port.
:return: integer or -1
"""
return self._portid
@property
def protocol(self):
"""
Accessor for protocol
:return: string
"""
return self._protocol
@property
def state(self):
"""
Accessor for service's state (open, filtered, closed,...)
:return: string
"""
return self._state['state'] if 'state' in self._state else None
@property
def reason(self):
"""
Accessor for service's state reason (syn-ack, filtered,...)
:return: string or empty if not applicable
"""
return self._reason
@property
def reason_ip(self):
"""
Accessor for service's state reason ip
:return: string or empty if not applicable
"""
return self._reason_ip
@property
def reason_ttl(self):
"""
Accessor for service's state reason ttl
:return: string or empty if not applicable
"""
return self._reason_ttl
@property
def service(self):
"""
Accessor for service name.
:return: string or empty
"""
return self._service['name'] if 'name' in self._service else ''
@property
def service_dict(self):
"""
Accessor for service dictionary.
:return: dict or None
"""
return self._service
def open(self):
"""
Tells if the port was open or not
:return: boolean
"""
return 'state' in self._state and self._state['state'] == 'open'
@property
def product(self):
if 'product' in self._service.keys():
return self._service['product'].rstrip()
else:
return None
@property
def product_version(self):
if 'version' in self._service.keys():
return self._service['version'].rstrip()
else:
return None
@property
def product_extrainfo(self):
if 'extrainfo' in self._service.keys():
return self._service['extrainfo'].rstrip()
else:
return None
@property
def owner(self):
"""
Accessor for service owner if available
"""
return self._owner
@property
def banner(self):
"""
Accessor for the service's banner. Only available
if the nmap option -sV or similar was used.
:return: string
"""
notrelevant = ['name', 'method', 'conf', 'cpelist',
'servicefp', 'tunnel']
relevant = ['product', 'version', 'extrainfo']
b = ''
skeys = self._service.keys()
if 'method' in self._service and self._service['method'] == "probed":
for relk in relevant:
if relk in skeys:
b += '{0}: {1} '.format(relk, self._service[relk])
for mkey in skeys:
if mkey not in notrelevant and mkey not in relevant:
b += '{0}: {1} '.format(mkey, self._service[mkey])
return b.rstrip()
@property
def cpelist(self):
"""
Accessor for list of CPE for this particular service
"""
return self._cpelist
@property
def scripts_results(self):
"""
Gives a python list of the nse scripts results.
The dict key is the name (id) of the nse script and
the value is the output of the script.
:return: dict
"""
scripts_dict = None
try:
scripts_dict = self._service_extras['scripts']
except (KeyError, TypeError):
pass
return scripts_dict
@property
def servicefp(self):
"""
Accessor for the service's fingerprint
if the nmap option -sV or -A is used
:return: string if available
"""
return self._servicefp
@property
def tunnel(self):
"""
Accessor for the service's tunnel type
if applicable and available from scan
results
:return: string if available
"""
return self._tunnel
@property
def id(self):
"""
Accessor for the id() of the NmapService.
This is used for diff()ing NmapService object via NmapDiff.
:return: tuple
"""
return "{0}.{1}".format(self.protocol, self.port)
def get_dict(self):
"""
Return a python dict representation of the NmapService object.
This is used to diff() NmapService objects via NmapDiff.
:return: dict
"""
return ({'id': str(self.id), 'port': str(self.port),
'protocol': self.protocol, 'banner': self.banner,
'service': self.service, 'state': self.state,
'reason': self.reason})
def diff(self, other):
"""
Calls NmapDiff to check the difference between self and
another NmapService object.
Will return a NmapDiff object.
This objects return python set() of keys describing the elements
which have changed, were added, removed or kept unchanged.
:return: NmapDiff object
"""
return NmapDiff(self, other)
================================================
FILE: libnmap/parser.py
================================================
# -*- coding: utf-8 -*-
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
from libnmap.objects import NmapHost, NmapService, NmapReport
class NmapParser(object):
@classmethod
def parse(cls, nmap_data=None, data_type='XML', incomplete=False):
"""
Generic class method of NmapParser class.
The data to be parsed does not need to be a complete nmap
scan report. You can possibly give <hosts>...</hosts>
or <port> XML tags.
:param nmap_data: any portion of nmap scan result. \
nmap_data should always be a string representing a part \
or a complete nmap scan report.
:type nmap_data: string
:param data_type: specifies the type of data to be parsed.
:type data_type: string ("XML"|"JSON"|"YAML").
:param incomplete: enable you to parse interrupted nmap scans \
and/or incomplete nmap xml blocks by adding a </nmaprun> at \
the end of the scan.
:type incomplete: boolean
As of today, only XML parsing is supported.
:return: NmapObject (NmapHost, NmapService or NmapReport)
"""
nmapobj = None
if data_type == "XML":
nmapobj = cls._parse_xml(nmap_data, incomplete)
else:
raise NmapParserException("Unknown data type provided. "
"Please check documentation for "
"supported data types.")
return nmapobj
@classmethod
def _parse_xml(cls, nmap_data=None, incomplete=False):
"""
Protected class method used to process a specific data type.
In this case: XML. This method is called by cls.parse class
method and receives nmap scan results data (in XML).
:param nmap_data: any portion of nmap scan result can be given \
as argument. nmap_data should always be a string representing \
a part or a complete nmap scan report.
:type nmap_data: string
This method checks which portion of a nmap scan is given \
as argument.
It could be:
1. a full nmap scan report;
2. a scanned host: <host> tag in a nmap scan report
3. a scanned service: <port> tag
4. a list of hosts: <hosts/> tag (TODO)
5. a list of ports: <ports/> tag
:param incomplete: enable you to parse interrupted nmap scans \
and/or incomplete nmap xml blocks by adding a </nmaprun> at \
the end of the scan.
:type incomplete: boolean
:return: NmapObject (NmapHost, NmapService or NmapReport) \
or a list of NmapObject
"""
if not nmap_data:
raise NmapParserException("No report data to parse: please "
"provide a valid XML nmap report")
elif not isinstance(nmap_data, str):
raise NmapParserException("wrong nmap_data type given as "
"argument: cannot parse data")
if incomplete is True:
nmap_data += "</nmaprun>"
try:
root = ET.fromstring(nmap_data)
except:
raise NmapParserException("Wrong XML structure: cannot parse data")
nmapobj = None
if root.tag == 'nmaprun':
nmapobj = cls._parse_xml_report(root)
elif root.tag == 'host':
nmapobj = cls._parse_xml_host(root)
elif root.tag == 'ports':
nmapobj = cls._parse_xml_ports(root)
elif root.tag == 'port':
nmapobj = cls._parse_xml_port(root)
else:
raise NmapParserException("Unpexpected data structure for XML "
"root node")
return nmapobj
@classmethod
def _parse_xml_report(cls, root=None):
"""
This method parses out a full nmap scan report from its XML root
node: <nmaprun>.
:param root: Element from xml.ElementTree (top of XML the document)
:type root: Element
:return: NmapReport object
"""
nmap_scan = {'_nmaprun': {}, '_scaninfo': {},
'_hosts': [], '_runstats': {}}
if root is None:
raise NmapParserException("No root node provided to parse XML "
"report")
nmap_scan['_nmaprun'] = cls.__format_attributes(root)
for el in root:
if el.tag == 'scaninfo':
nmap_scan['_scaninfo'] = cls.__parse_scaninfo(el)
elif el.tag == 'host':
nmap_scan['_hosts'].append(cls._parse_xml_host(el))
elif el.tag == 'runstats':
nmap_scan['_runstats'] = cls.__parse_runstats(el)
# else:
# print "struct pparse unknown attr: {0} value: {1}".format(
# el.tag,
# el.get(el.tag))
return NmapReport(nmap_scan)
@classmethod
def parse_fromstring(cls, nmap_data, data_type="XML", incomplete=False):
"""
Call generic cls.parse() method and ensure that a string is \
passed on as argument. If not, an exception is raised.
:param nmap_data: Same as for parse(), any portion of nmap scan. \
Reports could be passed as argument. Data type _must_ be a string.
:type nmap_data: string
:param data_type: Specifies the type of data passed on as argument.
:param incomplete: enable you to parse interrupted nmap scans \
and/or incomplete nmap xml blocks by adding a </nmaprun> at \
the end of the scan.
:type incomplete: boolean
:return: NmapObject
"""
if not isinstance(nmap_data, str):
raise NmapParserException("bad argument type for "
"xarse_fromstring(): should be a string")
return cls.parse(nmap_data, data_type, incomplete)
@classmethod
def parse_fromfile(cls, nmap_report_path,
data_type="XML",
incomplete=False):
"""
Call generic cls.parse() method and ensure that a correct file \
path is given as argument. If not, an exception is raised.
:param nmap_data: Same as for parse(). \
Any portion of nmap scan reports could be passed as argument. \
Data type _must be a valid path to a file containing \
nmap scan results.
:param data_type: Specifies the type of serialization in the file.
:param incomplete: enable you to parse interrupted nmap scans \
and/or incomplete nmap xml blocks by adding a </nmaprun> at \
the end of the scan.
:type incomplete: boolean
:return: NmapObject
"""
try:
with open(nmap_report_path, 'r') as fileobj:
fdata = fileobj.read()
rval = cls.parse(fdata, data_type, incomplete)
except IOError:
raise
return rval
@classmethod
def parse_fromdict(cls, rdict):
"""
Strange method which transforms a python dict \
representation of a NmapReport and turns it into an \
NmapReport object. \
Needs to be reviewed and possibly removed.
:param rdict: python dict representation of an NmapReport
:type rdict: dict
:return: NmapReport
"""
nreport = {}
if list(rdict.keys())[0] == '__NmapReport__':
r = rdict['__NmapReport__']
nreport['_runstats'] = r['_runstats']
nreport['_scaninfo'] = r['_scaninfo']
nreport['_nmaprun'] = r['_nmaprun']
hlist = []
for h in r['_hosts']:
slist = []
for s in h['__NmapHost__']['_services']:
cname = '__NmapService__'
slist.append(NmapService(portid=s[cname]['_portid'],
protocol=s[cname]['_protocol'],
state=s[cname]['_state'],
owner=s[cname]['_owner'],
service=s[cname]['_service']))
nh = NmapHost(starttime=h['__NmapHost__']['_starttime'],
endtime=h['__NmapHost__']['_endtime'],
address=h['__NmapHost__']['_address'],
status=h['__NmapHost__']['_status'],
hostnames=h['__NmapHost__']['_hostnames'],
extras=h['__NmapHost__']['_extras'],
services=slist)
hlist.append(nh)
nreport['_hosts'] = hlist
nmapobj = NmapReport(nreport)
return nmapobj
@classmethod
def __parse_scaninfo(cls, scaninfo_data):
"""
Private method parsing a portion of a nmap scan result.
Receives a <scaninfo> XML tag.
:param scaninfo_data: <scaninfo> XML tag from a nmap scan
:type scaninfo_data: xml.ElementTree.Element or a string
:return: python dict representing the XML scaninfo tag
"""
xelement = cls.__format_element(scaninfo_data)
return cls.__format_attributes(xelement)
@classmethod
def _parse_xml_host(cls, scanhost_data):
"""
Protected method parsing a portion of a nmap scan result.
Receives a <host> XML tag representing a scanned host with
its services.
:param scaninfo_data: <host> XML tag from a nmap scan
:type scaninfo_data: xml.ElementTree.Element or a string
:return: NmapHost object
"""
xelement = cls.__format_element(scanhost_data)
_host_header = cls.__format_attributes(xelement)
_hostnames = []
_services = []
_status = {}
_addresses = []
_host_extras = {}
extra_tags = ['uptime', 'distance', 'tcpsequence',
'ipidsequence', 'tcptssequence', 'times']
for xh in xelement:
if xh.tag == 'hostnames':
for hostname in cls.__parse_hostnames(xh):
_hostnames.append(hostname)
elif xh.tag == 'ports':
ports_dict = cls._parse_xml_ports(xh)
for port in ports_dict['ports']:
_services.append(port)
_host_extras['extraports'] = ports_dict['extraports']
elif xh.tag == 'status':
_status = cls.__format_attributes(xh)
elif xh.tag == 'address':
_addresses.append(cls.__format_attributes(xh))
elif xh.tag == 'os':
_os_extra = cls.__parse_os_fingerprint(xh)
_host_extras.update({'os': _os_extra})
elif xh.tag == 'hostscript':
_host_scripts = cls.__parse_host_scripts(xh)
_host_extras.update({'hostscript': _host_scripts})
elif xh.tag in extra_tags:
_host_extras[xh.tag] = cls.__format_attributes(xh)
# else:
# print "struct host unknown attr: %s value: %s" %
# (h.tag, h.get(h.tag))
_stime = ''
_etime = ''
if 'starttime' in _host_header:
_stime = _host_header['starttime']
if 'endtime' in _host_header:
_etime = _host_header['endtime']
nhost = NmapHost(_stime,
_etime,
_addresses,
_status,
_hostnames,
_services,
_host_extras)
return nhost
@classmethod
def __parse_hostnames(cls, scanhostnames_data):
"""
Private method parsing the hostnames list within a <host> XML tag.
:param scanhostnames_data: <hostnames> XML tag from a nmap scan
:type scanhostnames_data: xml.ElementTree.Element or a string
:return: list of hostnames
"""
xelement = cls.__format_element(scanhostnames_data)
hostnames = []
for hname in xelement:
if hname.tag == 'hostname':
hostnames.append(hname.get('name'))
return hostnames
@classmethod
def _parse_xml_ports(cls, scanports_data):
"""
Protected method parsing the list of scanned services from
a targeted host. This protected method cannot be called directly
with a string. A <ports/> tag can be directly passed to parse()
and the below method will be called and return a list of nmap
scanned services.
:param scanports_data: <ports> XML tag from a nmap scan
:type scanports_data: xml.ElementTree.Element or a string
:return: list of NmapService
"""
xelement = cls.__format_element(scanports_data)
rdict = {'ports': [], 'extraports': None}
for xservice in xelement:
if xservice.tag == 'port':
nport = cls._parse_xml_port(xservice)
rdict['ports'].append(nport)
elif xservice.tag == 'extraports':
extraports = cls.__parse_extraports(xservice)
rdict['extraports'] = extraports
# else:
# print "struct port unknown attr: %s value: %s" %
# (h.tag, h.get(h.tag))
return rdict
@classmethod
def _parse_xml_port(cls, scanport_data):
"""
Protected method parsing a scanned service from a targeted host.
This protected method cannot be called directly.
A <port/> tag can be directly passed to parse() and the below
method will be called and return a NmapService object
representing the state of the service.
:param scanport_data: <port> XML tag from a nmap scan
:type scanport_data: xml.ElementTree.Element or a string
:return: NmapService
"""
xelement = cls.__format_element(scanport_data)
_port = cls.__format_attributes(xelement)
_portid = _port['portid'] if 'portid' in _port else None
_protocol = _port['protocol'] if 'protocol' in _port else None
_state = None
_service = None
_owner = None
_service_scripts = []
_service_extras = {}
for xport in xelement:
if xport.tag == 'state':
_state = cls.__format_attributes(xport)
elif xport.tag == 'service':
_service = cls.__parse_service(xport)
elif xport.tag == 'owner':
_owner = cls.__format_attributes(xport)
elif xport.tag == 'script':
_script_dict = cls.__parse_script(xport)
_service_scripts.append(_script_dict)
_service_extras['scripts'] = _service_scripts
if(_portid is None or _protocol is None or _state is None):
raise NmapParserException("XML <port> tag is incomplete. One "
"of the following tags is missing: "
"portid, protocol or state or tag.")
nport = NmapService(_portid,
_protocol,
_state,
_service,
_owner,
_service_extras)
return nport
@classmethod
def __parse_service(cls, xserv):
"""
Parse <service> tag to manage CPE object
"""
_service = cls.__format_attributes(xserv)
_cpelist = []
for _servnode in xserv:
if _servnode.tag == 'cpe':
_cpe_string = _servnode.text
_cpelist.append(_cpe_string)
_service['cpelist'] = _cpelist
return _service
@classmethod
def __parse_extraports(cls, extraports_data):
"""
Private method parsing the data from extra scanned ports.
X extraports were in state "closed" server returned "conn-refused"
tag: <extraports>
:param extraports_data: XML data for extraports
:type extraports_data: xml.ElementTree.Element or a string
:return: python dict with following keys: state, count, reason
"""
rdict = {'state': '', 'count': '', 'reasons': []}
xelement = cls.__format_element(extraports_data)
extraports_dict = cls.__format_attributes(xelement)
if 'state' in extraports_dict:
rdict['state'] = extraports_dict
if 'count' in extraports_dict:
rdict['count'] = extraports_dict
for xelt in xelement:
if xelt.tag == 'extrareasons':
extrareasons_dict = cls.__format_attributes(xelt)
rdict['reasons'].append(extrareasons_dict)
return rdict
@classmethod
def __parse_script(cls, script_data):
"""
Private method parsing the data from NSE scripts output
:param script_data: portion of XML describing the results of the
script data
:type script_data: xml.ElementTree.Element or a string
:return: python dict holding scripts output
"""
_script_dict = cls.__format_attributes(script_data)
_elt_dict = {}
for script_elem in script_data:
if script_elem.tag == 'elem':
_elt_dict.update({script_elem.get('key'): script_elem.text})
elif script_elem.tag == 'table':
tdict = {}
for telem in script_elem:
tdict[telem.get('key')] = telem.text
_elt_dict[script_elem.get('key')] = tdict
_script_dict['elements'] = _elt_dict
return _script_dict
@classmethod
def __parse_host_scripts(cls, scripts_data):
"""
Private method parsing the data from scripts affecting
the target host.
Contents of <hostscript> is returned as a list of dict.
:param scripts_data: portion of XML describing the results of the
scripts data
:type scripts_data: xml.ElementTree.Element or a string
:return: python list holding scripts output in a dict
"""
_host_scripts = []
for xscript in scripts_data:
if xscript.tag == 'script':
_script_dict = cls.__parse_script(xscript)
_host_scripts.append(_script_dict)
return _host_scripts
@classmethod
def __parse_os_fingerprint(cls, os_data):
"""
Private method parsing the data from an OS fingerprint (-O).
Contents of <os> is returned as a dict.
:param os_data: portion of XML describing the results of the
os fingerprinting attempt
:type os_data: xml.ElementTree.Element or a string
:return: python dict representing the XML os tag
"""
rdict = {}
xelement = cls.__format_element(os_data)
os_class_probability = []
os_match_probability = []
os_ports_used = []
os_fingerprints = []
for xos in xelement:
# for nmap xml version < 1.04, osclass is not
# embedded in osmatch
if xos.tag == 'osclass':
os_class_proba = cls.__parse_osclass(xos)
os_class_probability.append(os_class_proba)
elif xos.tag == 'osmatch':
os_match_proba = cls.__parse_osmatch(xos)
os_match_probability.append(os_match_proba)
elif xos.tag == 'portused':
os_portused = cls.__format_attributes(xos)
os_ports_used.append(os_portused)
elif xos.tag == 'osfingerprint':
os_fp_dict = cls.__format_attributes(xos)
os_fingerprints.append(os_fp_dict)
rdict['osmatches'] = os_match_probability
rdict['osclasses'] = os_class_probability
rdict['ports_used'] = os_ports_used
rdict['osfingerprints'] = os_fingerprints
return rdict
@classmethod
def __parse_osmatch(cls, osmatch_data):
"""
This methods parses osmatch data and returns a dict. Depending
on the nmap xml version, osmatch could contain an osclass
dict.
:param osmatch_data: <osmatch> XML tag from a nmap scan
:type osmatch_data: xml.ElementTree.Element or a string
:return: python dict representing the XML osmatch tag
"""
rdict = {}
xelement = cls.__format_element(osmatch_data)
rdict['osmatch'] = cls.__format_attributes(xelement)
rdict['osclasses'] = []
for xmltag in xelement:
if xmltag.tag == 'osclass':
_osclass_dict = cls.__parse_osclass(xmltag)
rdict['osclasses'].append(_osclass_dict)
else:
exmsg = "Unexcepted node in <osmatch>: {0}".format(xmltag.tag)
raise NmapParserException(exmsg)
return rdict
@classmethod
def __parse_osclass(cls, osclass_data):
"""
This methods parses osclass data and returns a dict. Depending
on the nmap xml version, osclass could contain a cpe
dict.
:param osclass_data: <osclass> XML tag from a nmap scan
:type osclass_data: xml.ElementTree.Element or a string
:return: python dict representing the XML osclass tag
"""
rdict = {}
xelement = cls.__format_element(osclass_data)
rdict['osclass'] = cls.__format_attributes(xelement)
rdict['cpe'] = []
for xmltag in xelement:
if xmltag.tag == 'cpe':
_cpe_string = xmltag.text
rdict['cpe'].append(_cpe_string)
else:
exmsg = "Unexcepted node in <osclass>: {0}".format(xmltag.tag)
raise NmapParserException(exmsg)
return rdict
@classmethod
def __parse_runstats(cls, scanrunstats_data):
"""
Private method parsing a portion of a nmap scan result.
Receives a <runstats> XML tag.
:param scanrunstats_data: <runstats> XML tag from a nmap scan
:type scanrunstats_data: xml.ElementTree.Element or a string
:return: python dict representing the XML runstats tag
"""
xelement = cls.__format_element(scanrunstats_data)
rdict = {}
for xmltag in xelement:
if xmltag.tag in ['finished', 'hosts']:
rdict[xmltag.tag] = cls.__format_attributes(xmltag)
else:
exmsg = "Unexcepted node in <runstats>: {0}".format(xmltag.tag)
raise NmapParserException(exmsg)
return rdict
@staticmethod
def __format_element(elt_data):
"""
Private method which ensures that a XML portion to be parsed is
of type xml.etree.ElementTree.Element.
If elt_data is a string, then it is converted to an
XML Element type.
:param elt_data: XML Element to be parsed or string
to be converted to a XML Element
:return: Element
"""
if isinstance(elt_data, str):
try:
xelement = ET.fromstring(elt_data)
except:
raise NmapParserException("Error while trying "
"to instanciate XML Element from "
"string {0}".format(elt_data))
elif ET.iselement(elt_data):
xelement = elt_data
else:
raise NmapParserException("Error while trying to parse supplied "
"data: unsupported format")
return xelement
@staticmethod
def __format_attributes(elt_data):
"""
Private method which converts a single XML tag to a python dict.
It also checks that the elt_data given as argument is of type
xml.etree.ElementTree.Element
:param elt_data: XML Element to be parsed or string
to be converted to a XML Element
:return: Element
"""
rval = {}
if not ET.iselement(elt_data):
raise NmapParserException("Error while trying to parse supplied "
"data attributes: format is not XML or "
"XML tag is empty")
try:
for dkey in elt_data.keys():
rval[dkey] = elt_data.get(dkey)
if rval[dkey] is None:
raise NmapParserException("Error while trying to build-up "
"element attributes: empty "
"attribute {0}".format(dkey))
except:
raise
return rval
class NmapParserException(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
================================================
FILE: libnmap/plugins/__init__.py
================================================
================================================
FILE: libnmap/plugins/backend_host.py
================================================
#!/usr/bin/env python
from sqlalchemy import create_engine
from sqlalchemy.schema import Column
from sqlalchemy.types import Integer, DateTime, LargeBinary, Text, String, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from libnmap.plugins.backendplugin import NmapBackendPlugin
from libnmap.reportjson import ReportEncoder, ReportDecoder
import json
from datetime import datetime
Base = declarative_base()
class NmapSqlPlugin(NmapBackendPlugin):
"""
This class handle the persistence of NmapRepport object in SQL backend
Implementation is made using sqlalchemy(0.8.1)
usage :
#get a nmapReport object
from libnmap.parser import NmapParser
from libnmap.reportjson import ReportDecoder, ReportEncoder
import json
nmap_report_obj = NmapParser.parse_fromfile(
'/home/vagrant/python-nmap-lib/libnmap/test/files/1_hosts.xml')
#get a backend with in memory sqlite
from libnmap.plugins.backendpluginFactory import BackendPluginFactory
mybackend_mem = BackendPluginFactory.create(plugin_name='sql',
url='sqlite://',
echo=True)
mybackend_mysql = BackendPluginFactory.create(plugin_name='sql',
url='mysql+mysqldb://scott:tiger@localhost/foo',
echo=True)
mybackend = BackendPluginFactory.create(plugin_name='sql',
url='sqlite:////tmp/reportdb.sql',
echo=True)
#lets save!!
nmap_report_obj.save(mybackend)
mybackend.getall()
mybackend.get(1)
"""
class Reports(Base):
"""
Embeded class for ORM map NmapReport to a
simple three column table
"""
__tablename__ = 'result_ip'
id = Column('id', Integer, primary_key=True)
taskid = Column('taskid', Integer)
inserted = Column('inserted', DateTime(), default='now')
domain = Column('domain', String(256))
address = Column('address', String(32))
is_up = Column('is_up', Boolean)
os = Column('os', String(256))
def __init__(self, obj_NmapReport):
self.inserted = datetime.fromtimestamp(int(obj_NmapReport.endtime))
self.taskid = obj_NmapReport.taskid
self.is_up = obj_NmapReport.is_up()
if len(obj_NmapReport.hostnames) > 0:
self.domain = obj_NmapReport.hostnames[0]
else:
self.domain = None
self.address = obj_NmapReport.address
if len(obj_NmapReport.os.osmatch()) > 0:
self.os = obj_NmapReport.os.osmatch()[0]
else:
self.os = None
def decode(self):
json_decoded = self.report_json.decode('utf-8')
nmap_report_obj = json.loads(json_decoded,
cls=ReportDecoder)
return nmap_report_obj
def __init__(self, **kwargs):
"""
constructor receive a **kwargs as the **kwargs in the sqlalchemy
create_engine() method (see sqlalchemy docs)
You must add to this **kwargs an 'url' key with the url to your
database
This constructor will :
- create all the necessary obj to discuss with the DB
- create all the mapping(ORM)
todo : suport the : sqlalchemy.engine_from_config
:param **kwargs:
:raises: ValueError if no url is given,
all exception sqlalchemy can throw
ie sqlite in memory url='sqlite://' echo=True
ie sqlite file on hd url='sqlite:////tmp/reportdb.sql' echo=True
ie mysql url='mysql+mysqldb://scott:tiger@localhost/foo'
"""
NmapBackendPlugin.__init__(self)
self.engine = None
self.url = None
self.Session = sessionmaker()
if 'url' not in kwargs:
raise ValueError
self.url = kwargs['url']
del kwargs['url']
try:
self.engine = create_engine(self.url, **kwargs)
Base.metadata.create_all(bind=self.engine, checkfirst=True)
self.Session.configure(bind=self.engine)
except:
raise
def insert(self, nmap_report):
"""
insert NmapReport in the backend
:param NmapReport:
:returns: the ident of the object in the backend for future usage \
or None
"""
sess = self.Session()
report = NmapSqlPlugin.Reports(nmap_report)
sess.add(report)
sess.commit()
reportid = report.id
sess.close()
return reportid if reportid else None
def get(self, report_id=None):
"""
retreive a NmapReport from the backend
:param id: str
:returns: NmapReport
"""
if report_id is None:
raise ValueError
sess = self.Session()
our_report = (
sess.query(NmapSqlPlugin.Reports).filter_by(id=report_id).first())
sess.close()
return our_report.decode() if our_report else None
def getall(self):
"""
:param filter: Nice to have implement a filter capability
:returns: collection of tuple (id,NmapReport)
"""
sess = self.Session()
nmapreportList = []
for report in (
sess.query(NmapSqlPlugin.Reports).
order_by(NmapSqlPlugin.Reports.inserted)):
nmapreportList.append((report.id, report.decode()))
sess.close()
return nmapreportList
def delete(self, report_id=None):
"""
Remove a report from the backend
:param id: str
:returns: The number of rows deleted
"""
if report_id is None:
raise ValueError
nb_line = 0
sess = self.Session()
nb_line = sess.query(NmapSqlPlugin.Reports).\
filter_by(id=report_id).delete()
sess.commit()
sess.close()
return nb_line
================================================
FILE: libnmap/plugins/backend_service.py
================================================
#!/usr/bin/env python
from sqlalchemy import create_engine
from sqlalchemy.schema import Column
from sqlalchemy.types import Integer, DateTime, LargeBinary, Text, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from libnmap.plugins.backendplugin import NmapBackendPlugin
from libnmap.reportjson import ReportEncoder, ReportDecoder
import json
from datetime import datetime
import binascii
Base = declarative_base()
class NmapSqlPlugin(NmapBackendPlugin):
"""
This class handle the persistence of NmapRepport object in SQL backend
Implementation is made using sqlalchemy(0.8.1)
usage :
#get a nmapReport object
from libnmap.parser import NmapParser
from libnmap.reportjson import ReportDecoder, ReportEncoder
import json
nmap_report_obj = NmapParser.parse_fromfile(
'/home/vagrant/python-nmap-lib/libnmap/test/files/1_hosts.xml')
#get a backend with in memory sqlite
from libnmap.plugins.backendpluginFactory import BackendPluginFactory
mybackend_mem = BackendPluginFactory.create(plugin_name='sql',
url='sqlite://',
echo=True)
mybackend_mysql = BackendPluginFactory.create(plugin_name='sql',
url='mysql+mysqldb://scott:tiger@localhost/foo',
echo=True)
mybackend = BackendPluginFactory.create(plugin_name='sql',
url='sqlite:////tmp/reportdb.sql',
echo=True)
#lets save!!
nmap_report_obj.save(mybackend)
mybackend.getall()
mybackend.get(1)
"""
class Reports(Base):
"""
Embeded class for ORM map NmapReport to a
simple three column table
"""
__tablename__ = 'result_ports'
id = Column('id', Integer, primary_key=True)
taskid = Column('taskid', Integer)
inserted = Column('inserted', DateTime(), default='now')
address = Column('address', String(256))
port = Column('port', Integer)
service = Column('service', String(256))
state = Column('state', String(12))
protocol = Column('protocol', String(12))
product = Column('product', String(64))
product_version = Column('product_version', String(64))
product_extrainfo = Column('product_extrainfo', String(128))
# banner = Column('banner', String(256))
scripts_results = Column('scripts_results', Text)
def __init__(self, obj_NmapReport):
self.inserted = datetime.fromtimestamp(int(obj_NmapReport.endtime))
self.taskid = obj_NmapReport.taskid
self.address = obj_NmapReport.address
self.port = obj_NmapReport.port
self.service = obj_NmapReport.service
self.state = obj_NmapReport.state
self.protocol = str(obj_NmapReport.protocol)
self.product = str(obj_NmapReport.product)
self.product_version = str(obj_NmapReport.product_version)
self.product_extrainfo = str(obj_NmapReport.product_extrainfo)
# self.banner = str(obj_NmapReport.banner)
# self.scripts_results = binascii.b2a_hex(str(obj_NmapReport.scripts_results))
if len(obj_NmapReport.scripts_results) > 0:
self.scripts_results = obj_NmapReport.scripts_results[0]['output']
else:
self.scripts_results = None
def decode(self):
json_decoded = self.report_json.decode('utf-8')
nmap_report_obj = json.loads(json_decoded,
cls=ReportDecoder)
return nmap_report_obj
def __init__(self, **kwargs):
"""
constructor receive a **kwargs as the **kwargs in the sqlalchemy
create_engine() method (see sqlalchemy docs)
You must add to this **kwargs an 'url' key with the url to your
database
This constructor will :
- create all the necessary obj to discuss with the DB
- create all the mapping(ORM)
todo : suport the : sqlalchemy.engine_from_config
:param **kwargs:
:raises: ValueError if no url is given,
all exception sqlalchemy can throw
ie sqlite in memory url='sqlite://' echo=True
ie sqlite file on hd url='sqlite:////tmp/reportdb.sql' echo=True
ie mysql url='mysql+mysqldb://scott:tiger@localhost/foo'
"""
NmapBackendPlugin.__init__(self)
self.engine = None
self.url = None
self.Session = sessionmaker()
if 'url' not in kwargs:
raise ValueError
self.url = kwargs['url']
del kwargs['url']
try:
self.engine = create_engine(self.url, **kwargs)
Base.metadata.create_all(bind=self.engine, checkfirst=True)
self.Session.configure(bind=self.engine)
except:
raise
def insert(self, nmap_report):
"""
insert NmapReport in the backend
:param NmapReport:
:returns: the ident of the object in the backend for future usage \
or None
"""
sess = self.Session()
report = NmapSqlPlugin.Reports(nmap_report)
sess.add(report)
sess.commit()
reportid = report.id
sess.close()
return reportid if reportid else None
def get(self, report_id=None):
"""
retreive a NmapReport from the backend
:param id: str
:returns: NmapReport
"""
if report_id is None:
raise ValueError
sess = self.Session()
our_report = (
sess.query(NmapSqlPlugin.Reports).filter_by(id=report_id).first())
sess.close()
return our_report.decode() if our_report else None
def getall(self):
"""
:param filter: Nice to have implement a filter capability
:returns: collection of tuple (id,NmapReport)
"""
sess = self.Session()
nmapreportList = []
for report in (
sess.query(NmapSqlPlugin.Reports).
order_by(NmapSqlPlugin.Reports.inserted)):
nmapreportList.append((report.id, report.decode()))
sess.close()
return nmapreportList
def delete(self, report_id=None):
"""
Remove a report from the backend
:param id: str
:returns: The number of rows deleted
"""
if report_id is None:
raise ValueError
nb_line = 0
sess = self.Session()
nb_line = sess.query(NmapSqlPlugin.Reports).\
filter_by(id=report_id).delete()
sess.commit()
sess.close()
return nb_line
================================================
FILE: libnmap/plugins/backendplugin.py
================================================
#!/usr/bin/env python
class NmapBackendPlugin(object):
"""
Abstract class showing to the minimal implementation for a plugin
All subclass MUST at least implement the following methods
"""
def __init__(self):
self.dbname = 'nmapdb'
self.store = 'reports'
def insert(self, NmapReport):
"""
insert NmapReport in the backend
:param NmapReport:
:return: str the ident of the object in the backend for
future usage
or None
"""
raise NotImplementedError
def delete(self, id):
"""
delete NmapReport if the backend
:param id: str
"""
raise NotImplementedError
def get(self, id):
"""
retreive a NmapReport from the backend
:param id: str
:return: NmapReport
"""
raise NotImplementedError
def getall(self, filter):
"""
:return: collection of tuple (id,NmapReport)
:param filter: Nice to have implement a filter capability
"""
raise NotImplementedError
================================================
FILE: libnmap/plugins/backendpluginFactory.py
================================================
#!/usr/bin/env python
import sys
import inspect
class BackendPluginFactory(object):
"""
This is a backend plugin factory a backend instance MUST be
created via the static method create()
ie : mybackend = BackendPluginFactory.create()
"""
@classmethod
def create(cls, plugin_name="mongodb", **kwargs):
"""Import the needed lib and return an object NmapBackendPlugin
representing the backend of your desire.
NmapBackendPlugin is an abstract class, to know what argument
need to be given, review the code of the subclass you need
:param plugin_name: str : name of the py file without .py
:return: NmapBackend (abstract class on top of all plugin)
"""
backendplugin = None
plugin_path = "libnmap.plugins.{0}".format(plugin_name)
__import__(plugin_path)
pluginobj = sys.modules[plugin_path]
pluginclasses = inspect.getmembers(pluginobj, inspect.isclass)
for classname, classobj in pluginclasses:
if inspect.getmodule(classobj).__name__.find(plugin_path) == 0:
try:
backendplugin = classobj(**kwargs)
except Exception as error:
raise Exception("Cannot create Backend: {0}".format(error))
return backendplugin
================================================
FILE: libnmap/plugins/es.py
================================================
# -*- coding: utf-8 -*-
import json
from libnmap.reportjson import ReportEncoder
from libnmap.plugins.backendplugin import NmapBackendPlugin
from elasticsearch import Elasticsearch
from datetime import datetime
class NmapElasticsearchPlugin(NmapBackendPlugin):
"""
This class enables the user to store and manipulate nmap reports \
in a elastic search db.
"""
def __init__(self, index=None):
if index is None:
self.index = "nmap.{0}".format(datetime.now().strftime('%Y-%m-%d'))
else:
self.index = index
self._esapi = Elasticsearch()
def insert(self, report, doc_type=None):
"""
insert NmapReport in the backend
:param NmapReport:
:return: str the ident of the object in the backend for
future usage
or None
"""
if doc_type is None:
doc_type = 'NmapReport'
j = json.dumps(report, cls=ReportEncoder)
res = self._esapi.index(
index=self.index,
doc_type=doc_type,
body=json.loads(j))
rc = res['_id']
return rc
def delete(self, id):
"""
delete NmapReport if the backend
:param id: str
"""
raise NotImplementedError
def get(self, id):
"""
retreive a NmapReport from the backend
:param id: str
:return: NmapReport
"""
res = self._esapi.get(index=self.index,
doc_type="NmapReport",
id=id)['_source']
return res
def getall(self, filter=None):
"""
:return: collection of tuple (id,NmapReport)
:param filter: Nice to have implement a filter capability
"""
rsearch = self._esapi.search(index=self.index,
body={"query": {"match_all": {}}})
print("--------------------")
print(type(rsearch))
print(rsearch)
print("------------")
================================================
FILE: libnmap/plugins/mongodb.py
================================================
#!/usr/bin/env python
import json
from pymongo import MongoClient
from bson.objectid import ObjectId
from libnmap.reportjson import ReportEncoder
from libnmap.parser import NmapParser
from libnmap.plugins.backendplugin import NmapBackendPlugin
class NmapMongodbPlugin(NmapBackendPlugin):
"""
This class handle the persistence of NmapRepport object in mongodb
Implementation is made using pymongo
Object of this class must be create via the
BackendPluginFactory.create(**url) where url is a named dict like
{'plugin_name': "mongodb"} this dict may reeive all the param
MongoClient() support
"""
def __init__(self, dbname=None, store=None, **kwargs):
NmapBackendPlugin.__init__(self)
if dbname is not None:
self.dbname = dbname
if store is not None:
self.store = store
self.dbclient = MongoClient(**kwargs)
self.collection = self.dbclient[self.dbname][self.store]
def insert(self, report):
"""
create a json object from an NmapReport instance
:param NmapReport: obj to insert
:return: str id
"""
j = json.dumps(report, cls=ReportEncoder)
try:
oid = self.collection.insert(json.loads(j))
except:
raise Exception("Failed to insert nmap object in MongoDB")
return str(oid)
def get(self, str_report_id=None):
""" select a NmapReport by Id
:param str: id
:return: NmapReport object
"""
rid = str_report_id
nmapreport = None
if str_report_id is not None and isinstance(str_report_id, str):
rid = ObjectId(str_report_id)
if isinstance(rid, ObjectId):
# get a specific report by mongo's id
resultset = self.collection.find({'_id': rid})
if resultset.count() == 1:
# search by id means only one in the iterator
record = resultset[0]
# remove mongo's id to recreate the NmapReport Obj
del record['_id']
nmapreport = NmapParser.parse_fromdict(record)
return nmapreport
def getall(self, dict_filter=None):
"""return a list of tuple (id,NmapReport) saved in the backend
TODO : add a filter capability
"""
nmapreportlist = []
resultset = self.collection.find()
for report in resultset:
oid = report['_id']
del report['_id']
nmapreport = NmapParser.parse_fromdict(report)
nmapreportlist.append((oid, nmapreport))
return nmapreportlist
def delete(self, report_id=None):
"""
delete an obj from the backend
:param str: id
:return: dict document with result or None
"""
if report_id is not None and isinstance(report_id, str):
return self.collection.remove({'_id': ObjectId(report_id)})
else:
return self.collection.remove({'_id': report_id})
================================================
FILE: libnmap/plugins/s3.py
================================================
#!/usr/bin/env python
"""
:mod:`libnmap.plugin.s3` -- S3 Backend Plugin
=============================================
.. module:: libnmap.plugin.s3
:platform: Linux
:synopsis: a plugin is representation of a S3 backend using boto
.. moduleauthor:: Ronald Bister
.. moduleauthor:: Mike Boutillier
"""
import json
from bson.objectid import ObjectId
from boto.s3.connection import S3Connection, OrdinaryCallingFormat
from boto.s3.key import Key
from boto.s3.bucketlistresultset import bucket_lister
from boto.exception import S3ResponseError
from libnmap.reportjson import ReportEncoder
from libnmap.parser import NmapParser
from libnmap.plugins.backendplugin import NmapBackendPlugin
class NmapS3Plugin(NmapBackendPlugin):
"""
This plugin save the reports on S3 and compatible.
"""
def __init__(self, **kwargs):
"""
- create the conn object
- create the bucket (if it doesn't exist)
- if not given, awsaccessKey_nmapreport
- may raise exception (ie in case of conflict bucket name)
- sample :
To connect to walrus:
from libnmap.plugins.backendpluginFactory import
BackendPluginFactory
walrusBackend =
BackendPluginFactory.create(
plugin_name='s3',
host="walrus.ecc.eucalyptus.com",
path="/services/Walrus",port=8773,
is_secure=False,
aws_access_key_id='UU72FLVJCAYRATLXI70YH',
aws_secret_access_key=
'wFg7gP5YFHjVlxakw1g1uCC8UR2xVW5ax9ErZCut')
To connect to S3:
mybackend_S3 =
BackendPluginFactory.create(
plugin_name='s3',
is_secure=True,
aws_access_key_id='MYACCESSKEY',
aws_secret_access_key='MYSECRET')
"""
NmapBackendPlugin.__init__(self)
try:
calling_format = OrdinaryCallingFormat()
if 'bucket' not in kwargs:
self.bucket_name = ''.join(
[kwargs['aws_access_key_id'].lower(),
"_nmapreport"])
else:
self.bucket_name = kwargs['bucket']
del kwargs['bucket']
kwargs['calling_format'] = calling_format
self.conn = S3Connection(**kwargs)
self.bucket = self.conn.lookup(self.bucket_name)
if self.bucket is None:
self.bucket = self.conn.create_bucket(self.bucket_name)
except:
raise
def insert(self, report):
"""
create a json string from an NmapReport instance
and push it to S3 bucket.
:param NmapReport: obj to insert
:rtype: string
:return: str id
:todo: Add tagging option
"""
try:
oid = ObjectId()
mykey = Key(self.bucket)
mykey.key = str(oid)
strjsonnmapreport = json.dumps(report, cls=ReportEncoder)
mykey.set_contents_from_string(strjsonnmapreport)
except:
raise Exception("Failed to add nmap object in s3 bucket")
return str(oid)
def get(self, str_report_id=None):
"""
select a NmapReport by Id.
:param str: id
:rtype: NmapReport
:return: NmapReport object
"""
nmapreport = None
if str_report_id is not None and isinstance(str_report_id, str):
try:
mykey = Key(self.bucket)
mykey.key = str_report_id
nmapreportjson = json.loads(mykey.get_contents_as_string())
nmapreport = NmapParser.parse_fromdict(nmapreportjson)
except S3ResponseError:
pass
return nmapreport
def getall(self, dict_filter=None):
"""
:rtype: List of tuple
:return: list of key/report
:todo: add a filter capability
"""
nmapreportlist = []
for key in bucket_lister(self.bucket):
if isinstance(key, Key):
nmapreportjson = json.loads(key.get_contents_as_string())
nmapreport = NmapParser.parse_fromdict(nmapreportjson)
nmapreportlist.append((key.key, nmapreport))
return nmapreportlist
def delete(self, report_id=None):
"""
delete an obj from the backend
:param str: id
:return: dict document with result or None
"""
rcode = None
if report_id is not None and isinstance(report_id, str):
rcode = self.bucket.delete_key(report_id)
return rcode
================================================
FILE: libnmap/plugins/sql.py
================================================
#!/usr/bin/env python
from sqlalchemy import create_engine
from sqlalchemy.schema import Column
from sqlalchemy.types import Integer, DateTime, LargeBinary, Text, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from libnmap.plugins.backendplugin import NmapBackendPlugin
from libnmap.reportjson import ReportEncoder, ReportDecoder
import json
from datetime import datetime
Base = declarative_base()
class NmapSqlPlugin(NmapBackendPlugin):
"""
This class handle the persistence of NmapRepport object in SQL backend
Implementation is made using sqlalchemy(0.8.1)
usage :
#get a nmapReport object
from libnmap.parser import NmapParser
from libnmap.reportjson import ReportDecoder, ReportEncoder
import json
nmap_report_obj = NmapParser.parse_fromfile(
'/home/vagrant/python-nmap-lib/libnmap/test/files/1_hosts.xml')
#get a backend with in memory sqlite
from libnmap.plugins.backendpluginFactory import BackendPluginFactory
mybackend_mem = BackendPluginFactory.create(plugin_name='sql',
url='sqlite://',
echo=True)
mybackend_mysql = BackendPluginFactory.create(plugin_name='sql',
url='mysql+mysqldb://scott:tiger@localhost/foo',
echo=True)
mybackend = BackendPluginFactory.create(plugin_name='sql',
url='sqlite:////tmp/reportdb.sql',
echo=True)
#lets save!!
nmap_report_obj.save(mybackend)
mybackend.getall()
mybackend.get(1)
"""
class Reports(Base):
"""
Embeded class for ORM map NmapReport to a
simple three column table
"""
__tablename__ = 'reports'
id = Column('report_id', Integer, primary_key=True)
inserted = Column('inserted', DateTime(), default='now')
host = Column('host', String(256))
command_line = Column('command_line', String(256))
report_json = Column('report_json', LargeBinary())
def __init__(self, obj_NmapReport):
# self.inserted = datetime.fromtimestamp(obj_NmapReport.endtime)
self.command_line = str(obj_NmapReport.status)
self.host = obj_NmapReport.address
self.command_line =
dumped_json = json.dumps(obj_NmapReport,
cls=ReportEncoder)
self.report_json = bytes(dumped_json.encode('UTF-8'))
def decode(self):
json_decoded = self.report_json.decode('utf-8')
nmap_report_obj = json.loads(json_decoded,
cls=ReportDecoder)
return nmap_report_obj
def __init__(self, **kwargs):
"""
constructor receive a **kwargs as the **kwargs in the sqlalchemy
create_engine() method (see sqlalchemy docs)
You must add to this **kwargs an 'url' key with the url to your
database
This constructor will :
- create all the necessary obj to discuss with the DB
- create all the mapping(ORM)
todo : suport the : sqlalchemy.engine_from_config
:param **kwargs:
:raises: ValueError if no url is given,
all exception sqlalchemy can throw
ie sqlite in memory url='sqlite://' echo=True
ie sqlite file on hd url='sqlite:////tmp/reportdb.sql' echo=True
ie mysql url='mysql+mysqldb://scott:tiger@localhost/foo'
"""
NmapBackendPlugin.__init__(self)
self.engine = None
self.url = None
self.Session = sessionmaker()
if 'url' not in kwargs:
raise ValueError
self.url = kwargs['url']
del kwargs['url']
try:
self.engine = create_engine(self.url, **kwargs)
Base.metadata.create_all(bind=self.engine, checkfirst=True)
self.Session.configure(bind=self.engine)
except:
raise
def insert(self, nmap_report):
"""
insert NmapReport in the backend
:param NmapReport:
:returns: the ident of the object in the backend for future usage \
or None
"""
sess = self.Session()
report = NmapSqlPlugin.Reports(nmap_report)
sess.add(report)
sess.commit()
reportid = report.id
sess.close()
return reportid if reportid else None
def get(self, report_id=None):
"""
retreive a NmapReport from the backend
:param id: str
:returns: NmapReport
"""
if report_id is None:
raise ValueError
sess = self.Session()
our_report = (
sess.query(NmapSqlPlugin.Reports).filter_by(id=report_id).first())
sess.close()
return our_report.decode() if our_report else None
def getall(self):
"""
:param filter: Nice to have implement a filter capability
:returns: collection of tuple (id,NmapReport)
"""
sess = self.Session()
nmapreportList = []
for report in (
sess.query(NmapSqlPlugin.Reports).
order_by(NmapSqlPlugin.Reports.inserted)):
nmapreportList.append((report.id, report.decode()))
sess.close()
return nmapreportList
def delete(self, report_id=None):
"""
Remove a report from the backend
:param id: str
:returns: The number of rows deleted
"""
if report_id is None:
raise ValueError
nb_line = 0
sess = self.Session()
nb_line = sess.query(NmapSqlPlugin.Reports).\
filter_by(id=report_id).delete()
sess.commit()
sess.close()
return nb_line
================================================
FILE: libnmap/process.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import pwd
import shlex
import subprocess
import multiprocessing
from threading import Thread
from xml.dom import pulldom
import warnings
try:
from Queue import Empty, Full
except ImportError:
from queue import Empty, Full
__all__ = [
'NmapProcess'
]
class NmapTask(object):
"""
NmapTask is a internal class used by process. Each time nmap
starts a new task during the scan, a new class will be instanciated.
Classes examples are: "Ping Scan", "NSE script", "DNS Resolve",..
To each class an estimated time to complete is assigned and updated
at least every second within the NmapProcess.
A property NmapProcess.current_task points to the running task at
time T and a dictionnary NmapProcess.tasks with "task name" as key
is built during scan execution
"""
def __init__(self, name, starttime=0, extrainfo=''):
self.name = name
self.etc = 0
self.progress = 0
self.percent = 0
self.remaining = 0
self.status = 'started'
self.starttime = starttime
self.endtime = 0
self.extrainfo = extrainfo
self.updated = 0
class NmapProcess(Thread):
"""
NmapProcess is a class which wraps around the nmap executable.
Consequently, in order to run an NmapProcess, nmap should be installed
on the host running the script. By default NmapProcess will produce
the output of the nmap scan in the nmap XML format. This could be then
parsed out via the NmapParser class from libnmap.parser module.
"""
def __init__(self, targets="127.0.0.1",
options="-sT", event_callback=None, safe_mode=True, fqp=None):
"""
Constructor of NmapProcess class.
:param targets: hosts to be scanned. Could be a string of hosts \
separated with a coma or a python list of hosts/ip.
:type targets: string or list
:param options: list of nmap options to be applied to scan. \
These options are all documented in nmap's man pages.
:param event_callback: callable function which will be ran \
each time nmap process outputs data. This function will receive \
two parameters:
1. the nmap process object
2. the data produced by nmap process. See readme for examples.
:param safe_mode: parameter to protect unsafe options like -oN, -oG, \
-iL, -oA,...
:param fqp: full qualified path, if None, nmap will be searched \
in the PATH
:return: NmapProcess object
"""
Thread.__init__(self)
unsafe_opts = set(['-oG', '-oN', '-iL', '-oA', '-oS', '-oX',
'--iflist', '--resume', '--stylesheet',
'--datadir'])
if fqp:
if os.path.isfile(fqp) and os.access(fqp, os.X_OK):
self.__nmap_binary = fqp
else:
raise EnvironmentError(1, "wrong path or not executable", fqp)
else:
nmap_binary_name = "nmap"
self.__nmap_binary = self._whereis(nmap_binary_name)
# self.__nmap_fixed_options = "-oX - -vvv --stats-every 1s"
self.__nmap_fixed_options = "-oX -"
if self.__nmap_binary is None:
raise EnvironmentError(1, "nmap is not installed or could "
"not be found in system path")
if isinstance(targets, str):
self.__nmap_targets = targets.replace(" ", "").split(',')
elif isinstance(targets, list):
self.__nmap_targets = targets
else:
raise Exception("Supplied target list should be either a "
"string or a list")
self._nmap_options = set(options.split())
if safe_mode and not self._nmap_options.isdisjoint(unsafe_opts):
raise Exception("unsafe options activated while safe_mode "
"is set True")
self.__nmap_dynamic_options = options
self.__sudo_run = ''
self.__nmap_command_line = self.get_command_line()
if event_callback and callable(event_callback):
self.__nmap_event_callback = event_callback
else:
self.__nmap_event_callback = None
(self.DONE, self.READY, self.RUNNING,
self.CANCELLED, self.FAILED) = range(5)
self._run_init()
def _run_init(self):
self.__process_killed = multiprocessing.Event()
self.__nmap_command_line = self.get_command_line()
# API usable in callback function
self.__nmap_proc = None
self.__qout = None
self.__nmap_rc = 0
self.__state = self.RUNNING
self.__starttime = 0
self.__endtime = 0
self.__version = ''
self.__elapsed = ''
self.__summary = ''
self.__stdout = ''
self.__stderr = ''
self.__current_task = ''
self.__nmap_tasks = {}
def _whereis(self, program):
"""
Protected method enabling the object to find the full path of a binary
from its PATH environment variable.
:param program: name of a binary for which the full path needs to
be discovered.
:return: the full path to the binary.
:todo: add a default path list in case PATH is empty.
"""
for path in os.environ.get('PATH', '').split(':'):
if (os.path.exists(os.path.join(path, program)) and not
os.path.isdir(os.path.join(path, program))):
return os.path.join(path, program)
return None
def get_command_line(self):
"""
Public method returning the reconstructed command line ran via the lib
:return: the full nmap command line to run
:rtype: string
"""
return ("{0} {1} {2} {3} {4}".format(self.__sudo_run,
self.__nmap_binary,
self.__nmap_fixed_options,
self.__nmap_dynamic_options,
" ".join(self.__nmap_targets)))
def sudo_run(self, run_as='root'):
"""
Public method enabling the library's user to run the scan with
priviledges via sudo. The sudo configuration should be set manually
on the local system otherwise sudo will prompt for a password.
This method alters the command line by prefixing the sudo command to
nmap and will then call self.run()
:param run_as: user name to which the lib needs to sudo to run the scan
:return: return code from nmap execution
"""
sudo_user = run_as.split().pop()
try:
pwd.getpwnam(sudo_user).pw_uid
except KeyError:
_exmsg = ("Username {0} does not exists. Please supply"
" a valid username".format(run_as))
raise EnvironmentError(_exmsg)
sudo_path = self._whereis("sudo")
if sudo_path is None:
raise EnvironmentError(2, "sudo is not installed or "
"could not be found in system path: "
"cannot run nmap with sudo")
self.__sudo_run = "{0} -u {1}".format(sudo_path, sudo_user)
rc = self.run()
self.__sudo_run = ""
return rc
def sudo_run_background(self, run_as='root'):
"""
Public method enabling the library's user to run in background a
nmap scan with priviledges via sudo.
The sudo configuration should be set manually on the local system
otherwise sudo will prompt for a password.
This method alters the command line by prefixing the sudo command to
nmap and will then call self.run()
:param run_as: user name to which the lib needs to sudo to run the scan
:return: return code from nmap execution
"""
sudo_user = run_as.split().pop()
try:
pwd.getpwnam(sudo_user).pw_uid
except KeyError:
_exmsg = ("Username {0} does not exists. Please supply"
" a valid username".format(run_as))
raise EnvironmentError(_exmsg)
sudo_path = self._whereis("sudo")
if sudo_path is None:
raise EnvironmentError(2, "sudo is not installed or "
"could not be found in system path: "
"cannot run nmap with sudo")
self.__sudo_run = "{0} -u {1}".format(sudo_path, sudo_user)
super(NmapProcess, self).start()
def run(self):
"""
Public method which is usually called right after the constructor
of NmapProcess. This method starts the nmap executable's subprocess.
It will also bind a Process that will read from subprocess' stdout
and stderr and push the lines read in a python queue for futher
processing. This processing is waken-up each time data is pushed
from the nmap binary into the stdout reading routine. Processing
could be performed by a user-provided callback. The whole
NmapProcess object could be accessible asynchroneously.
return: return code from nmap execution
"""
def ioreader_routine(proc_stdout, io_queue, data_pushed, producing):
"""
local function that will read lines from a file descriptor
and put the data in a python queue for futher processing.
:param proc_stdout: file descriptor to read lines from.
:param io_queue: queue in which read lines will be pushed.
:param data_pushed: queue used to push data read from the
nmap stdout back into the parent process
:param producing: shared variable to notify the parent process
that processing is either running, either over.
"""
producing.value = 1
for streamline in iter(proc_stdout.readline, b''):
if self.__process_killed.is_set():
break
if streamline is not None:
try:
io_queue.put(str(streamline.decode()))
except Full:
pass
data_pushed.set()
producing.value = 0
data_pushed.set()
self._run_init()
producing = multiprocessing.Value('i', 1)
data_pushed = multiprocessing.Event()
self.__qout = multiprocessing.Queue()
_tmp_cmdline = shlex.split(self.__nmap_command_line)
try:
self.__nmap_proc = subprocess.Popen(args=_tmp_cmdline,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
bufsize=0)
ioreader = multiprocessing.Process(target=ioreader_routine,
args=(self.__nmap_proc.stdout,
self.__qout,
data_pushed,
producing))
# ioreader.daemon=False
ioreader.start()
self.__state = self.RUNNING
except OSError:
self.__state = self.FAILED
raise EnvironmentError(1, "nmap is not installed or could "
"not be found in system path")
thread_stream = ''
while(self.__nmap_proc.poll() is None or producing.value == 1):
if self.__process_killed.is_set():
break
if producing.value == 1 and self.__qout.empty():
try:
data_pushed.wait()
except KeyboardInterrupt:
break
try:
thread_stream = self.__qout.get_nowait()
except Empty:
pass
except KeyboardInterrupt:
break
else:
self.__stdout += thread_stream
evnt = self.__process_event(thread_stream)
if self.__nmap_event_callback and evnt:
self.__nmap_event_callback(self)
data_pushed.clear()
ioreader.join()
# queue clean-up
while not self.__qout.empty():
self.__stdout += self.__qout.get_nowait()
self.__stderr += str(self.__nmap_proc.stderr.read().decode())
self.__nmap_rc = self.__nmap_proc.poll()
if self.rc is None:
self.__state = self.CANCELLED
elif self.rc == 0:
self.__state = self.DONE
if self.current_task:
self.__nmap_tasks[self.current_task.name].progress = 100
else:
self.__state = self.FAILED
return self.rc
def run_background(self):
"""
run nmap scan in background as a thread.
For privileged scans, consider NmapProcess.sudo_run_background()
"""
self.__state = self.RUNNING
super(NmapProcess, self).start()
def is_running(self):
"""
Checks if nmap is still running.
:return: True if nmap is still running
"""
return self.state == self.RUNNING
def has_terminated(self):
"""
Checks if nmap has terminated. Could have failed or succeeded
:return: True if nmap process is not running anymore.
"""
return (self.state == self.DONE or self.state == self.FAILED
or self.state == self.CANCELLED)
def has_failed(self):
"""
Checks if nmap has failed.
:return: True if nmap process errored.
"""
return self.state == self.FAILED
def is_successful(self):
"""
Checks if nmap terminated successfully.
:return: True if nmap terminated successfully.
"""
return self.state == self.DONE
def stop(self):
"""
Send KILL -15 to the nmap subprocess and gently ask the threads to
stop.
"""
self.__state = self.CANCELLED
if self.__nmap_proc.poll() is None:
self.__nmap_proc.kill()
self.__qout.cancel_join_thread()
self.__process_killed.set()
def __process_event(self, eventdata):
"""
Private method called while nmap process is running. It enables the
library to handle specific data/events produced by nmap process.
So far, the following events are supported:
1. task progress: updates estimated time to completion and percentage
done while scan is running. Could be used in combination with a
callback function which could then handle this data while scan is
running.
2. nmap run: header of the scan. Usually displayed when nmap is started
3. finished: when nmap scan ends.
:return: True is event is known.
:todo: handle parsing directly via NmapParser.parse()
"""
rval = False
try:
edomdoc = pulldom.parseString(eventdata)
for xlmnt, xmlnode in edomdoc:
if xlmnt is not None and xlmnt == pulldom.START_ELEMENT:
if (xmlnode.nodeName == 'taskbegin' and
xmlnode.attributes.keys()):
xt = xmlnode.attributes
taskname = xt['task'].value
starttime = xt['time'].value
xinfo = ''
if 'extrainfo' in xt.keys():
xinfo = xt['extrainfo'].value
newtask = NmapTask(taskname, starttime, xinfo)
self.__nmap_tasks[newtask.name] = newtask
self.__current_task = newtask.name
rval = True
elif (xmlnode.nodeName == 'taskend' and
xmlnode.attributes.keys()):
xt = xmlnode.attributes
tname = xt['task'].value
xinfo = ''
self.__nmap_tasks[tname].endtime = xt['time'].value
if 'extrainfo' in xt.keys():
xinfo = xt['extrainfo'].value
self.__nmap_tasks[tname].extrainfo = xinfo
self.__nmap_tasks[tname].status = "ended"
rval = True
elif (xmlnode.nodeName == 'taskprogress' and
xmlnode.attributes.keys()):
xt = xmlnode.attributes
tname = xt['task'].value
percent = xt['percent'].value
etc = xt['etc'].value
remaining = xt['remaining'].value
updated = xt['time'].value
self.__nmap_tasks[tname].percent = percent
self.__nmap_tasks[tname].progress = percent
self.__nmap_tasks[tname].etc = etc
self.__nmap_tasks[tname].remaining = remaining
self.__nmap_tasks[tname].updated = updated
rval = True
elif (xmlnode.nodeName == 'nmaprun' and
xmlnode.attributes.keys()):
self.__starttime = xmlnode.attributes['start'].value
self.__version = xmlnode.attributes['version'].value
rval = True
elif (xmlnode.nodeName == 'finished' and
xmlnode.attributes.keys()):
self.__endtime = xmlnode.attributes['time'].value
self.__elapsed = xmlnode.attributes['elapsed'].value
self.__summary = xmlnode.attributes['summary'].value
rval = True
except:
pass
return rval
@property
def command(self):
"""
return the constructed nmap command or empty string if not
constructed yet.
:return: string
"""
return self.__nmap_command_line or ''
@property
def targets(self):
"""
Provides the list of targets to scan
:return: list of string
"""
return self.__nmap_targets
@property
def options(self):
"""
Provides the list of options for that scan
:return: list of string (nmap options)
"""
return self._nmap_options
@property
def state(self):
"""
Accessor for nmap execution state. Possible states are:
- self.READY
- self.RUNNING
- self.FAILED
- self.CANCELLED
- self.DONE
:return: integer (from above documented enum)
"""
return self.__state
@property
def starttime(self):
"""
Accessor for time when scan started
:return: string. Unix timestamp
"""
return self.__starttime
@property
def endtime(self):
"""
Accessor for time when scan ended
:return: string. Unix timestamp
"""
warnings.warn("data collected from finished events are deprecated."
"Use NmapParser.parse()", DeprecationWarning)
return self.__endtime
@property
def elapsed(self):
"""
Accessor returning for how long the scan ran (in seconds)
:return: string
"""
warnings.warn("data collected from finished events are deprecated."
"Use NmapParser.parse()", DeprecationWarning)
return self.__elapsed
@property
def summary(self):
"""
Accessor returning a short summary of the scan's results
:return: string
"""
warnings.warn("data collected from finished events are deprecated."
"Use NmapParser.parse()", DeprecationWarning)
return self.__summary
@property
def tasks(self):
"""
Accessor returning for the list of tasks ran during nmap scan
:return: dict of NmapTask object
"""
return self.__nmap_tasks
@property
def version(self):
"""
Accessor for nmap binary version number
:return: version number of nmap binary
:rtype: string
"""
return self.__version
@property
def current_task(self):
"""
Accessor for the current NmapTask beeing run
:return: NmapTask or None if no task started yet
"""
rval = None
if len(self.__current_task):
rval = self.tasks[self.__current_task]
return rval
@property
def etc(self):
"""
Accessor for estimated time to completion
:return: estimated time to completion
"""
rval = 0
if self.current_task:
rval = self.current_task.etc
return rval
@property
def progress(self):
"""
Accessor for progress status in percentage
:return: percentage of job processed.
"""
rval = 0
if self.current_task:
rval = self.current_task.progress
return rval
@property
def rc(self):
"""
Accessor for nmap execution's return code
:return: nmap execution's return code
"""
return self.__nmap_rc
@property
def stdout(self):
"""
Accessor for nmap standart output
:return: output from nmap scan in XML
:rtype: string
"""
return self.__stdout
@property
def stderr(self):
"""
Accessor for nmap standart error
:return: output from nmap when errors occured.
:rtype: string
"""
return self.__stderr
def main():
def mycallback(nmapscan=None):
if nmapscan.is_running() and nmapscan.current_task:
ntask = nmapscan.current_task
print("Task {0} ({1}): ETC: {2} DONE: {3}%".format(ntask.name,
ntask.status,
ntask.etc,
ntask.progress))
nm = NmapProcess("scanme.nmap.org",
options="-A",
event_callback=mycallback)
rc = nm.run()
if rc == 0:
print("Scan started at {0} nmap version: {1}").format(nm.starttime,
nm.version)
print("state: {0} (rc: {1})").format(nm.state, nm.rc)
print("results size: {0}").format(len(nm.stdout))
print("Scan ended {0}: {1}").format(nm.endtime, nm.summary)
else:
print("state: {0} (rc: {1})").format(nm.state, nm.rc)
print("Error: {stderr}").format(stderr=nm.stderr)
print("Result: {0}").format(nm.stdout)
if __name__ == '__main__':
main()
================================================
FILE: libnmap/reportjson.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import json
from libnmap.objects import NmapHost, NmapService, NmapReport
from libnmap.objects.os import NmapOSFingerprint, NmapOSMatch, NmapOSClass
from libnmap.objects.os import CPE, OSFPPortUsed
from libnmap.parser import NmapParser
class ReportEncoder(json.JSONEncoder):
def default(self, obj):
otype = {'NmapHost': NmapHost,
'NmapOSFingerprint': NmapOSFingerprint,
'NmapOSMatch': NmapOSMatch,
'NmapOSClass': NmapOSClass,
'CPE': CPE,
'OSFPPortUsed': OSFPPortUsed,
'NmapService': NmapService,
'NmapReport': NmapReport}
if isinstance(obj, tuple(otype.values())):
key = ('__{0}__').format(obj.__class__.__name__)
return {key: obj.__dict__}
return json.JSONEncoder.default(self, obj)
class ReportDecoder(json.JSONDecoder):
def decode(self, json_str):
r = NmapParser.parse_fromdict(json.loads(json_str))
return r
================================================
FILE: run.py
================================================
#!/usr/bin/env python
# encoding: utf-8
# tasks.py
# email: ringzero@0x557.org
import sys
from tasks import *
from wyfunc import make_target_list
def start_nmap_dispath(targets, taskid=None):
print '-' * 50
target_list = make_target_list(targets)
for target in target_list:
print '-' * 50
print '* push %s to Redis' % target
print '* AsyncResult:%s' % nmap_dispath.delay(target,taskid=taskid)
print '-' * 50
print '* Push nmapscan tasks complete.'
print '-' * 50
if __name__ == "__main__":
if len(sys.argv) == 2:
start_nmap_dispath(sys.argv[1])
sys.exit(0)
elif len(sys.argv) == 3:
start_nmap_dispath(sys.argv[1], sys.argv[2])
else:
print ("usage: %s targets taskid" % sys.argv[0])
sys.exit(-1)
================================================
FILE: src/supervisord_client.conf
================================================
; Sample supervisor config file.
;
; For more information on the config file, please see:
; http://supervisord.org/configuration.html
;
; Notes:
; - Shell expansion ("~" or "$HOME") is not supported. Environment
; variables can be expanded using this syntax: "%(ENV_HOME)s".
; - Comments must have a leading space: "a=b ;comment" not "a=b;comment".
[unix_http_server]
file=/tmp/supervisor.sock ; (the path to the socket file)
;chmod=0700 ; socket file mode (default 0700)
;chown=nobody:nogroup ; socket file uid:gid owner
;username=user ; (default is no username (open server))
;password=123 ; (default is no password (open server))
[inet_http_server] ; inet (TCP) server disabled by default
port=0.0.0.0:9001 ; (ip_address:port specifier, *:port for all iface)
;username=user ; (default is no username (open server))
;password=123 ; (default is no password (open server))
[supervisord]
logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log)
logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB)
logfile_backups=10 ; (num of main logfile rotation backups;default 10)
loglevel=info ; (log level;default info; others: debug,warn,trace)
pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
nodaemon=false ; (start in foreground if true;default false)
minfds=1024 ; (min. avail startup file descriptors;default 1024)
minprocs=200 ; (min. avail process descriptors;default 200)
;umask=022 ; (process file creation umask;default 022)
;user=chrism ; (default is current user, required if root)
;identifier=supervisor ; (supervisord identifier, default is 'supervisor')
;directory=/tmp ; (default is not to cd during start)
;nocleanup=true ; (don't clean up tempfiles at start;default false)
;childlogdir=/tmp ; ('AUTO' child log dir, default $TEMP)
;environment=KEY="value" ; (key value pairs to add to environment)
;strip_ansi=false ; (strip ansi escape codes in logs; def. false)
; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket
;username=chris ; should be same as http_username if set
;password=123 ; should be same as http_password if set
;prompt=mysupervisor ; cmd line prompt (default "supervisor")
;history_file=~/.sc_history ; use readline history if available
; The below sample program section shows all possible program subsection values,
; create one or more 'real' program: sections to be able to control them under
; supervisor.
;[program:theprogramname]
;command=/bin/cat ; the program (relative uses PATH, can take args)
;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
;numprocs=1 ; number of processes copies to start (def 1)
;directory=/tmp ; directory to cwd to before exec (def no cwd)
;umask=022 ; umask for process (default None)
;priority=999 ; the relative start priority (default 999)
;autostart=true ; start at supervisord start (default: true)
;autorestart=unexpected ; whether/when to restart (default: unexpected)
;startsecs=1 ; number of secs prog must stay running (def. 1)
;startretries=3 ; max # of serial start failures (default 3)
;exitcodes=0,2 ; 'expected' exit codes for process (default 0,2)
;stopsignal=QUIT ; signal used to kill process (default TERM)
;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10)
;stopasgroup=false ; send stop signal to the UNIX process group (default false)
;killasgroup=false ; SIGKILL the UNIX process group (def false)
;user=chrism ; setuid to this UNIX account to run the program
;redirect_stderr=true ; redirect proc stderr to stdout (default false)
;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO
;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stdout_logfile_backups=10 ; # of stdout logfile backups (default 10)
;stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0)
;stdout_events_enabled=false ; emit events on stdout writes (default false)
;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO
;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stderr_logfile_backups=10 ; # of stderr logfile backups (default 10)
;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0)
;stderr_events_enabled=false ; emit events on stderr writes (default false)
;environment=A="1",B="2" ; process environment additions (def no adds)
;serverurl=AUTO ; override serverurl computation (childutils)
; The below sample eventlistener section shows all possible
; eventlistener subsection values, create one or more 'real'
; eventlistener: sections to be able to handle event notifications
; sent by supervisor.
;[eventlistener:theeventlistenername]
;command=/bin/eventlistener ; the program (relative uses PATH, can take args)
;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
;numprocs=1 ; number of processes copies to start (def 1)
;events=EVENT ; event notif. types to subscribe to (req'd)
;buffer_size=10 ; event buffer queue size (default 10)
;directory=/tmp ; directory to cwd to before exec (def no cwd)
;umask=022 ; umask for process (default None)
;priority=-1 ; the relative start priority (default -1)
;autostart=true ; start at supervisord start (default: true)
;autorestart=unexpected ; whether/when to restart (default: unexpected)
;startsecs=1 ; number of secs prog must stay running (def. 1)
;startretries=3 ; max # of serial start failures (default 3)
;exitcodes=0,2 ; 'expected' exit codes for process (default 0,2)
;stopsignal=QUIT ; signal used to kill process (default TERM)
;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10)
;stopasgroup=false ; send stop signal to the UNIX process group (default false)
;killasgroup=false ; SIGKILL the UNIX process group (def false)
;user=chrism ; setuid to this UNIX account to run the program
;redirect_stderr=true ; redirect proc stderr to stdout (default false)
;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO
;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stdout_logfile_backups=10 ; # of stdout logfile backups (default 10)
;stdout_events_enabled=false ; emit events on stdout writes (default false)
;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO
;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stderr_logfile_backups ; # of stderr logfile backups (default 10)
;stderr_events_enabled=false ; emit events on stderr writes (default false)
;environment=A="1",B="2" ; process environment additions
;serverurl=AUTO ; override serverurl computation (childutils)
; The below sample group section shows all possible group values,
; create one or more 'real' group: sections to create "heterogeneous"
; process groups.
;[group:thegroupname]
;programs=progname1,progname2 ; each refers to 'x' in [program:x] definitions
;priority=999 ; the relative start priority (default 999)
; The [include] section can just contain the "files" setting. This
; setting can list multiple files (separated by whitespace or
; newlines). It can also contain wildcards. The filenames are
; interpreted as relative to this file. Included files *cannot*
; include files themselves.
[program:worker-ringzero]
command=celery -A tasks worker --loglevel=info --workdir=/home/thorns --concurrency=10 --hostname=%(program_name)s%(process_num)02d
directory=/home/thorns
user=root
process_name=%(program_name)s_%(process_num)02d
numprocs=1
autostart=true
autorestart=true
startetries=10
exitcodes=0
stopsignal=KILL
stopwaitsecs=10
redirect_stderr=true
;[include]
;files = relative/directory/*.ini
================================================
FILE: src/supervisord_server.conf
================================================
; Sample supervisor config file.
;
; For more information on the config file, please see:
; http://supervisord.org/configuration.html
;
; Notes:
; - Shell expansion ("~" or "$HOME") is not supported. Environment
; variables can be expanded using this syntax: "%(ENV_HOME)s".
; - Comments must have a leading space: "a=b ;comment" not "a=b;comment".
[unix_http_server]
file=/tmp/supervisor.sock ; (the path to the socket file)
;chmod=0700 ; socket file mode (default 0700)
;chown=nobody:nogroup ; socket file uid:gid owner
;username=user ; (default is no username (open server))
;password=123 ; (default is no password (open server))
[inet_http_server] ; inet (TCP) server disabled by default
port=0.0.0.0:9001 ; (ip_address:port specifier, *:port for all iface)
;username=user ; (default is no username (open server))
;password=123 ; (default is no password (open server))
[supervisord]
logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log)
logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB)
logfile_backups=10 ; (num of main logfile rotation backups;default 10)
loglevel=info ; (log level;default info; others: debug,warn,trace)
pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
nodaemon=false ; (start in foreground if true;default false)
minfds=1024 ; (min. avail startup file descriptors;default 1024)
minprocs=200 ; (min. avail process descriptors;default 200)
;umask=022 ; (process file creation umask;default 022)
;user=chrism ; (default is current user, required if root)
;identifier=supervisor ; (supervisord identifier, default is 'supervisor')
;directory=/tmp ; (default is not to cd during start)
;nocleanup=true ; (don't clean up tempfiles at start;default false)
;childlogdir=/tmp ; ('AUTO' child log dir, default $TEMP)
;environment=KEY="value" ; (key value pairs to add to environment)
;strip_ansi=false ; (strip ansi escape codes in logs; def. false)
; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket
;username=chris ; should be same as http_username if set
;password=123 ; should be same as http_password if set
;prompt=mysupervisor ; cmd line prompt (default "supervisor")
;history_file=~/.sc_history ; use readline history if available
; The below sample program section shows all possible program subsection values,
; create one or more 'real' program: sections to be able to control them under
; supervisor.
;[program:theprogramname]
;command=/bin/cat ; the program (relative uses PATH, can take args)
;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
;numprocs=1 ; number of processes copies to start (def 1)
;directory=/tmp ; directory to cwd to before exec (def no cwd)
;umask=022 ; umask for process (default None)
;priority=999 ; the relative start priority (default 999)
;autostart=true ; start at supervisord start (default: true)
;autorestart=unexpected ; whether/when to restart (default: unexpected)
;startsecs=1 ; number of secs prog must stay running (def. 1)
;startretries=3 ; max # of serial start failures (default 3)
;exitcodes=0,2 ; 'expected' exit codes for process (default 0,2)
;stopsignal=QUIT ; signal used to kill process (default TERM)
;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10)
;stopasgroup=false ; send stop signal to the UNIX process group (default false)
;killasgroup=false ; SIGKILL the UNIX process group (def false)
;user=chrism ; setuid to this UNIX account to run the program
;redirect_stderr=true ; redirect proc stderr to stdout (default false)
;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO
;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stdout_logfile_backups=10 ; # of stdout logfile backups (default 10)
;stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0)
;stdout_events_enabled=false ; emit events on stdout writes (default false)
;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO
;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stderr_logfile_backups=10 ; # of stderr logfile backups (default 10)
;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0)
;stderr_events_enabled=false ; emit events on stderr writes (default false)
;environment=A="1",B="2" ; process environment additions (def no adds)
;serverurl=AUTO ; override serverurl computation (childutils)
; The below sample eventlistener section shows all possible
; eventlistener subsection values, create one or more 'real'
; eventlistener: sections to be able to handle event notifications
; sent by supervisor.
;[eventlistener:theeventlistenername]
;command=/bin/eventlistener ; the program (relative uses PATH, can take args)
;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
;numprocs=1 ; number of processes copies to start (def 1)
;events=EVENT ; event notif. types to subscribe to (req'd)
;buffer_size=10 ; event buffer queue size (default 10)
;directory=/tmp ; directory to cwd to before exec (def no cwd)
;umask=022 ; umask for process (default None)
;priority=-1 ; the relative start priority (default -1)
;autostart=true ; start at supervisord start (default: true)
;autorestart=unexpected ; whether/when to restart (default: unexpected)
;startsecs=1 ; number of secs prog must stay running (def. 1)
;startretries=3 ; max # of serial start failures (default 3)
;exitcodes=0,2 ; 'expected' exit codes for process (default 0,2)
;stopsignal=QUIT ; signal used to kill process (default TERM)
;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10)
;stopasgroup=false ; send stop signal to the UNIX process group (default false)
;killasgroup=false ; SIGKILL the UNIX process group (def false)
;user=chrism ; setuid to this UNIX account to run the program
;redirect_stderr=true ; redirect proc stderr to stdout (default false)
;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO
;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stdout_logfile_backups=10 ; # of stdout logfile backups (default 10)
;stdout_events_enabled=false ; emit events on stdout writes (default false)
;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO
;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stderr_logfile_backups ; # of stderr logfile backups (default 10)
;stderr_events_enabled=false ; emit events on stderr writes (default false)
;environment=A="1",B="2" ; process environment additions
;serverurl=AUTO ; override serverurl computation (childutils)
; The below sample group section shows all possible group values,
; create one or more 'real' group: sections to create "heterogeneous"
; process groups.
;[group:thegroupname]
;programs=progname1,progname2 ; each refers to 'x' in [program:x] definitions
;priority=999 ; the relative start priority (default 999)
; The [include] section can just contain the "files" setting. This
; setting can list multiple files (separated by whitespace or
; newlines). It can also contain wildcards. The filenames are
; interpreted as relative to this file. Included files *cannot*
; include files themselves.
[program:flower]
command=celery flower --port=8080 --broker=redis://127.0.0.1:6379/0
directory=/home/thorns
process_name=%(program_name)s_%(process_num)02d
numprocs=1
autostart=true
autorestart=true
startetries=10
exitcodes=0
stopsignal=KILL
stopwaitsecs=10
redirect_stderr=true
[program:worker-ringzero]
command=celery -A tasks worker --loglevel=info --workdir=/home/thorns --concurrency=10 --hostname=%(program_name)s%(process_num)02d
directory=/home/thorns
user=root
process_name=%(program_name)s_%(process_num)02d
numprocs=1
autostart=true
autorestart=true
startetries=10
exitcodes=0
stopsignal=KILL
stopwaitsecs=10
redirect_stderr=true
;[include]
;files = relative/directory/*.ini
================================================
FILE: tasks.py
================================================
#!/usr/bin/env python
# encoding: utf-8
# tasks.py
# email: ringzero@0x557.org
'''
Thorns Project 分布式任务控制脚本
tasks
-- nmap_dispath # nmap 扫描调度函数
-- hydra_dispath # hydra 暴力破解调度函数
-- medusa_dispath # medusa 暴力破解调度函数
worker run()
--workdir=/home/thorns
'''
import subprocess
from celery import Celery, platforms
# 初始化芹菜对象
app = Celery()
# 允许celery以root权限启动
platforms.C_FORCE_ROOT = True
# 修改celery的全局配置
app.conf.update(
CELERY_IMPORTS = ("tasks", ),
BROKER_URL = 'redis://120.132.54.90:6379/0',
CELERY_RESULT_BACKEND = 'db+mysql://celery:celery1@3Wscan@42.62.52.62:443/wscan',
CELERY_TASK_SERIALIZER='json',
CELERY_RESULT_SERIALIZER='json',
CELERY_TIMEZONE='Asia/Shanghai',
CELERY_ENABLE_UTC=True,
CELERY_REDIS_MAX_CONNECTIONS=5000, # Redis 最大连接数
BROKER_TRANSPORT_OPTIONS = {'visibility_timeout': 3600}, # 如果任务没有在 可见性超时 内确认接收,任务会被重新委派给另一个Worker并执行 默认1 hour.
# BROKER_TRANSPORT_OPTIONS = {'fanout_prefix': True}, # 设置一个传输选项来给消息加上前缀
)
# 失败任务重启休眠时间300秒,最大重试次数5次
# @app.task(bind=True, default_retry_delay=300, max_retries=5)
@app.task
def nmap_dispath(targets, taskid=None):
# nmap环境参数配置
run_script_path = '/home/thorns'
if taskid == None:
cmdline = 'python wyportmap.py %s' % targets
else:
cmdline = 'python wyportmap.py %s %s' % (targets, taskid)
nmap_proc = subprocess.Popen(cmdline,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
process_output = nmap_proc.stdout.readlines()
return process_output
@app.task
def hydra_dispath(targets, protocol, userdic, passdic, taskid=None):
# 命令执行环境参数配置
run_script_path = '/home/thorns/script/hydra'
run_env = '{"LD_LIBRARY_PATH": "/home/thorns/libs/"}'
if taskid == None:
cmdline = 'python hydra.py %s %s %s %s' % (target, protocol, userdic, passdic)
else:
cmdline = 'python hydra.py %s %s %s %s %s' % (target, protocol, userdic, passdic, taskid)
nmap_proc = subprocess.Popen(cmdline,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,cwd=run_script_path,env=run_env)
process_output = nmap_proc.stdout.readlines()
return process_output
@app.task
def medusa_dispath(targets, protocol, userdic, passdic, taskid=None):
# 命令执行环境参数配置
run_script_path = '/home/thorns/script/medusa'
run_env = '{"LD_LIBRARY_PATH": "/home/thorns/libs/"}'
if taskid == None:
cmdline = 'python medusa.py %s %s %s %s' % (target, protocol, userdic, passdic)
else:
cmdline = 'python medusa.py %s %s %s %s %s' % (target, protocol, userdic, passdic, taskid)
nmap_proc = subprocess.Popen(cmdline,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,cwd=run_script_path,env=run_env)
process_output = nmap_proc.stdout.readlines()
return process_output
================================================
FILE: wyfunc.py
================================================
#!/usr/bin/env python
# encoding: utf-8
# wyfunc.py
# email: ringzero@0x557.org
def ip2num(ip):
ip = [int(x) for x in ip.split('.')]
return ip[0] << 24 | ip[1] << 16 | ip[2] << 8 | ip[3]
def num2ip(num):
return '%s.%s.%s.%s' % (
(num & 0xff000000) >> 24,
(num & 0x00ff0000) >> 16,
(num & 0x0000ff00) >> 8,
num & 0x000000ff
)
def gen_ips(start, end):
"""生成IP地址"""
# if num & 0xff 过滤掉 最后一段为 0 的IP
return [num2ip(num) for num in range(start, end + 1) if num & 0xff]
def make_ips_c_block(ipaddr):
address = {}
ipaddr = ipaddr.split('.')
ipaddr[3] = '0'
ipaddr = '.'.join(ipaddr)
address[ipaddr] = gen_ips(ip2num(ipaddr),ip2num(ipaddr) + 254)
return address
def ip_check(ip):
q = ip.split('.')
return len(q) == 4 and len(filter(lambda x: x >= 0 and x <= 255, \
map(int, filter(lambda x: x.isdigit(), q)))) == 4
def make_target_list(targets):
target_list = []
process_nmap_count = 1
startip, endip = targets.split('-')
if not ip_check(startip):
print '* StartIP format error'
else:
endip_len = len(endip.split('.'))
startip_num = ip2num(startip)
if endip_len == 1: # 结束IP为数字的场景
startip_endnum = startip.split('.')[3]
target_count = int(endip) - int(startip_endnum)
if target_count >= 0:
target_list.append(num2ip(startip_num))
# 每10个IP分配到一个wyportmap子进程上
# 更新:为每个独立的IP创建一个单独的任务,并删除掉开始IP,修改的时候注释掉上面那行
for i in xrange(0,(target_count+process_nmap_count-1)/process_nmap_count):
ip_count = (i * process_nmap_count) + process_nmap_count
remaining_count = target_count - ip_count
if remaining_count > 0:
scan_startip = num2ip(startip_num)
endip_num = startip_num + process_nmap_count
startip_num = endip_num
# target_option = '%s-%s' % (scan_startip, num2ip(endip_num))
target_option = num2ip(endip_num)
target_list.append(target_option)
else:
scan_startip = num2ip(startip_num)
endip_num = startip_num + process_nmap_count + remaining_count
startip_num = endip_num
# target_option = '%s-%s' % (scan_startip, num2ip(endip_num))
target_option = num2ip(endip_num)
target_list.append(target_option)
else:
print '* EndIP Less than StartIP'
elif endip_len == 4:
if ip_check(endip):
startip_num = ip2num(startip)
endip_num = ip2num(endip)
if startip_num <= endip_num:
target_count = endip_num - startip_num
if target_count >= 0:
target_list.append(num2ip(startip_num))
# 每10个IP分配到一个wyportmap子进程上
# 更新:为每个独立的IP创建一个单独的任务,并删除掉开始IP,修改的时候注释掉上面那行
for i in xrange(0,(target_count+process_nmap_count-1)/process_nmap_count):
ip_count = (i * process_nmap_count) + process_nmap_count
remaining_count = target_count - ip_count
if remaining_count > 0:
scan_startip = num2ip(startip_num)
endip_num = startip_num + process_nmap_count
startip_num = endip_num
# target_option = '%s-%s' % (scan_startip, num2ip(endip_num))
target_option = num2ip(endip_num)
target_list.append(target_option)
else:
scan_startip = num2ip(startip_num)
endip_num = startip_num + process_nmap_count + remaining_count
startip_num = endip_num
# target_option = '%s-%s' % (scan_startip, num2ip(endip_num))
target_option = num2ip(endip_num)
target_list.append(target_option)
else:
print '* EndIP Less than StartIP'
else:
print '* EndIP format error'
else:
print '* EndIP format error'
return target_list
================================================
FILE: wyportmap.py
================================================
#!/usr/bin/env python
# encoding: utf-8
# mail: ringzero@0x557.org
import json
import sys
from time import sleep
from libnmap.process import NmapProcess
from libnmap.reportjson import ReportDecoder, ReportEncoder
from libnmap.parser import NmapParser, NmapParserException
from libnmap.plugins.backendpluginFactory import BackendPluginFactory
# 重试次数 & 超时时间(s)
retrycnt = 3
timeout = 3600
# 数据库连接 & 全局扫描参数
global_dbcoon = 'mysql+mysqldb://celery:celery1@3Wscan@42.62.52.62:443/wscan'
# global_dbcoon = 'mysql+mysqldb://用户名:密码@数据库服务器IP:数据库端口/数据库名称'
global_options = '-sT -P0 -sV -O --script=banner -p T:21-25,80-89,110,143,443,513,873,1080,1433,1521,1158,3306-3308,3389,3690,5900,6379,7001,8000-8090,9000,9418,27017-27019,50060,111,11211,2049'
# 处理端口状态
global_log_states = ['open'] # open, filtered, closed, unfiltered
def do_nmap_scan(targets, options=global_options):
# 运行次数初始化
trycnt = 0
while True:
# 运行时间初始化
runtime = 0
if trycnt >= retrycnt:
# print '-' * 50
return 'retry overflow'
try:
nmap_proc = NmapProcess(targets=targets, options=options, safe_mode=False)
nmap_proc.run_background()
while nmap_proc.is_running():
if runtime >= timeout: # 运行超时,结束掉任务,休息1分钟, 再重启这个nmap任务
# print '-' * 50
# print "* timeout. terminate it..."
nmap_proc.stop()
# 休眠时间
sleep(60)
trycnt += 1
break
else:
# print 'running[%ss]:%s' % (runtime, nmap_proc.command)
sleep(5)
runtime += 5
if nmap_proc.is_successful():
# print '-' * 50
print nmap_proc.summary
return nmap_proc.stdout
except Exception, e:
# raise e
print e
trycnt += 1
if trycnt >= retrycnt:
# print '-' * 50
# print '* retry overflow'
return e
def parse_nmap_report(nmap_stdout, taskid=None):
try:
# 处理结果并写入后台数据库
nmap_report = NmapParser.parse(nmap_stdout)
# 声明后台对应的ORM数据库处理模型
my_services_backend = BackendPluginFactory.create(plugin_name='backend_service', url=global_dbcoon, echo=False, encoding='utf-8', pool_timeout=3600)
my_hosts_backend = BackendPluginFactory.create(plugin_name='backend_host', url=global_dbcoon, echo=False, encoding='utf-8', pool_timeout=3600)
# 开始处理扫描结果
for host in nmap_report.hosts:
# print("Nmap scan : {0}".format(host.address))
host.taskid = taskid
# 处理主机开放的服务和端口
for serv in host.services:
serv.address = host.address
serv.taskid = taskid
serv.endtime = host.endtime
if serv.state in global_log_states:
serv.save(my_services_backend)
host.save(my_hosts_backend)
return 'Scan finished'
except Exception, e:
# 处理报表出错,返回错误结果
return e
def run_wyportmap(targets, taskid=None):
# print '-' * 50
# print '* Starting id:(%s) [%s] portmap scan' % (taskid, targets)
# print '-' * 50
nmap_result = do_nmap_scan(targets)
# print '-' * 50
return parse_nmap_report(nmap_result,taskid)
if __name__ == "__main__":
if len(sys.argv) == 2:
print run_wyportmap(sys.argv[1])
sys.exit(0)
elif len(sys.argv) == 3:
print run_wyportmap(sys.argv[1], sys.argv[2])
else:
print ("usage: %s targets taskid" % sys.argv[0])
sys.exit(-1)
gitextract_4m8vr42h/ ├── 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/ │ ├── supervisord_client.conf │ └── supervisord_server.conf ├── tasks.py ├── wyfunc.py └── wyportmap.py
SYMBOL INDEX (285 symbols across 21 files)
FILE: libnmap/diff.py
class DictDiffer (line 4) | class DictDiffer(object):
method __init__ (line 12) | def __init__(self, current_dict, past_dict):
method added (line 19) | def added(self):
method removed (line 22) | def removed(self):
method changed (line 25) | def changed(self):
method unchanged (line 29) | def unchanged(self):
class NmapDiff (line 34) | class NmapDiff(DictDiffer):
method __init__ (line 57) | def __init__(self, nmap_obj1, nmap_obj2):
method __repr__ (line 74) | def __repr__(self):
class NmapDiffException (line 82) | class NmapDiffException(Exception):
method __init__ (line 83) | def __init__(self, msg):
FILE: libnmap/objects/cpe.py
class CPE (line 4) | class CPE(object):
method __init__ (line 12) | def __init__(self, cpestring):
method cpestring (line 23) | def cpestring(self):
method __repr__ (line 29) | def __repr__(self):
method get_part (line 32) | def get_part(self):
method get_vendor (line 38) | def get_vendor(self):
method get_product (line 44) | def get_product(self):
method get_version (line 50) | def get_version(self):
method get_update (line 56) | def get_update(self):
method get_edition (line 62) | def get_edition(self):
method get_language (line 68) | def get_language(self):
method is_application (line 74) | def is_application(self):
method is_hardware (line 80) | def is_hardware(self):
method is_operating_system (line 86) | def is_operating_system(self):
FILE: libnmap/objects/host.py
class NmapHost (line 7) | class NmapHost(object):
method __init__ (line 11) | def __init__(self, starttime='', endtime='', address=None, status=None,
method __eq__ (line 57) | def __eq__(self, other):
method __ne__ (line 72) | def __ne__(self, other):
method __repr__ (line 87) | def __repr__(self):
method __hash__ (line 97) | def __hash__(self):
method changed (line 106) | def changed(self, other):
method save (line 114) | def save(self, backend):
method starttime (line 123) | def starttime(self):
method endtime (line 132) | def endtime(self):
method address (line 141) | def address(self):
method address (line 150) | def address(self, addrdict):
method ipv4 (line 170) | def ipv4(self):
method mac (line 179) | def mac(self):
method vendor (line 188) | def vendor(self):
method ipv6 (line 197) | def ipv6(self):
method status (line 206) | def status(self):
method status (line 215) | def status(self, statusdict):
method is_up (line 226) | def is_up(self):
method hostnames (line 238) | def hostnames(self):
method services (line 247) | def services(self):
method get_ports (line 257) | def get_ports(self):
method get_open_ports (line 265) | def get_open_ports(self):
method get_service (line 274) | def get_service(self, portno, protocol='tcp'):
method get_service_byid (line 287) | def get_service_byid(self, service_id):
method os_class_probabilities (line 299) | def os_class_probabilities(self):
method os_match_probabilities (line 311) | def os_match_probabilities(self):
method os_fingerprinted (line 324) | def os_fingerprinted(self):
method os_fingerprint (line 333) | def os_fingerprint(self):
method os_ports_used (line 344) | def os_ports_used(self):
method tcpsequence (line 360) | def tcpsequence(self):
method ipsequence (line 375) | def ipsequence(self):
method uptime (line 389) | def uptime(self):
method lastboot (line 403) | def lastboot(self):
method distance (line 417) | def distance(self):
method scripts_results (line 431) | def scripts_results(self):
method id (line 445) | def id(self):
method extraports_state (line 454) | def extraports_state(self):
method extraports_reasons (line 469) | def extraports_reasons(self):
method get_dict (line 480) | def get_dict(self):
method diff (line 496) | def diff(self, other):
FILE: libnmap/objects/os.py
class OSFPPortUsed (line 7) | class OSFPPortUsed(object):
method __init__ (line 13) | def __init__(self, port_used_dict):
method state (line 22) | def state(self):
method proto (line 29) | def proto(self):
method portid (line 36) | def portid(self):
class NmapOSMatch (line 43) | class NmapOSMatch(object):
method __init__ (line 62) | def __init__(self, osmatch_dict):
method add_osclass (line 85) | def add_osclass(self, osclass_obj):
method osclasses (line 95) | def osclasses(self):
method name (line 102) | def name(self):
method line (line 109) | def line(self):
method accuracy (line 120) | def accuracy(self):
method get_cpe (line 128) | def get_cpe(self):
method __repr__ (line 143) | def __repr__(self):
class NmapOSClass (line 150) | class NmapOSClass(object):
method __init__ (line 160) | def __init__(self, osclass_dict):
method cpelist (line 184) | def cpelist(self):
method vendor (line 194) | def vendor(self):
method osfamily (line 203) | def osfamily(self):
method accuracy (line 212) | def accuracy(self):
method osgen (line 221) | def osgen(self):
method type (line 230) | def type(self):
method description (line 239) | def description(self):
method __repr__ (line 251) | def __repr__(self):
class NmapOSFingerprint (line 260) | class NmapOSFingerprint(object):
method __init__ (line 266) | def __init__(self, osfp_data):
method get_osmatch (line 292) | def get_osmatch(self, osclass_obj):
method _add_dummy_osmatch (line 312) | def _add_dummy_osmatch(self, osclass_obj):
method osmatches (line 329) | def osmatches(self, min_accuracy=0):
method fingerprint (line 339) | def fingerprint(self):
method fingerprints (line 343) | def fingerprints(self):
method ports_used (line 347) | def ports_used(self):
method osmatch (line 355) | def osmatch(self, min_accuracy=90):
method osclass (line 364) | def osclass(self, min_accuracy=90):
method os_cpelist (line 379) | def os_cpelist(self):
method __repr__ (line 386) | def __repr__(self):
FILE: libnmap/objects/report.py
class NmapReport (line 5) | class NmapReport(object):
method __init__ (line 23) | def __init__(self, raw_data=None):
method save (line 36) | def save(self, backend):
method diff (line 55) | def diff(self, other):
method started (line 72) | def started(self):
method commandline (line 87) | def commandline(self):
method version (line 96) | def version(self):
method scan_type (line 106) | def scan_type(self):
method hosts (line 116) | def hosts(self):
method get_host_byid (line 126) | def get_host_byid(self, host_id):
method endtime (line 143) | def endtime(self):
method endtimestr (line 157) | def endtimestr(self):
method summary (line 172) | def summary(self):
method elapsed (line 194) | def elapsed(self):
method hosts_up (line 209) | def hosts_up(self):
method hosts_down (line 225) | def hosts_down(self):
method hosts_total (line 241) | def hosts_total(self):
method get_raw_data (line 255) | def get_raw_data(self):
method __set_raw_data (line 268) | def __set_raw_data(self, raw_data):
method is_consistent (line 274) | def is_consistent(self):
method get_dict (line 290) | def get_dict(self):
method id (line 310) | def id(self):
method __eq__ (line 316) | def __eq__(self, other):
method __ne__ (line 334) | def __ne__(self, other):
method __repr__ (line 352) | def __repr__(self):
FILE: libnmap/objects/service.py
class NmapService (line 6) | class NmapService(object):
method __init__ (line 14) | def __init__(self, portid, protocol='tcp', state=None,
method __eq__ (line 75) | def __eq__(self, other):
method __ne__ (line 89) | def __ne__(self, other):
method __repr__ (line 103) | def __repr__(self):
method __hash__ (line 111) | def __hash__(self):
method changed (line 115) | def changed(self, other):
method save (line 125) | def save(self, backend):
method port (line 133) | def port(self):
method protocol (line 142) | def protocol(self):
method state (line 151) | def state(self):
method reason (line 160) | def reason(self):
method reason_ip (line 169) | def reason_ip(self):
method reason_ttl (line 178) | def reason_ttl(self):
method service (line 187) | def service(self):
method service_dict (line 196) | def service_dict(self):
method open (line 204) | def open(self):
method product (line 213) | def product(self):
method product_version (line 220) | def product_version(self):
method product_extrainfo (line 227) | def product_extrainfo(self):
method owner (line 234) | def owner(self):
method banner (line 241) | def banner(self):
method cpelist (line 263) | def cpelist(self):
method scripts_results (line 270) | def scripts_results(self):
method servicefp (line 287) | def servicefp(self):
method tunnel (line 297) | def tunnel(self):
method id (line 308) | def id(self):
method get_dict (line 318) | def get_dict(self):
method diff (line 331) | def diff(self, other):
FILE: libnmap/parser.py
class NmapParser (line 11) | class NmapParser(object):
method parse (line 13) | def parse(cls, nmap_data=None, data_type='XML', incomplete=False):
method _parse_xml (line 49) | def _parse_xml(cls, nmap_data=None, incomplete=False):
method _parse_xml_report (line 109) | def _parse_xml_report(cls, root=None):
method parse_fromstring (line 142) | def parse_fromstring(cls, nmap_data, data_type="XML", incomplete=False):
method parse_fromfile (line 168) | def parse_fromfile(cls, nmap_report_path,
method parse_fromdict (line 199) | def parse_fromdict(cls, rdict):
method __parse_scaninfo (line 242) | def __parse_scaninfo(cls, scaninfo_data):
method _parse_xml_host (line 257) | def _parse_xml_host(cls, scanhost_data):
method __parse_hostnames (line 318) | def __parse_hostnames(cls, scanhostnames_data):
method _parse_xml_ports (line 336) | def _parse_xml_ports(cls, scanports_data):
method _parse_xml_port (line 366) | def _parse_xml_port(cls, scanport_data):
method __parse_service (line 417) | def __parse_service(cls, xserv):
method __parse_extraports (line 431) | def __parse_extraports(cls, extraports_data):
method __parse_script (line 457) | def __parse_script(cls, script_data):
method __parse_host_scripts (line 482) | def __parse_host_scripts(cls, scripts_data):
method __parse_os_fingerprint (line 502) | def __parse_os_fingerprint(cls, os_data):
method __parse_osmatch (line 544) | def __parse_osmatch(cls, osmatch_data):
method __parse_osclass (line 569) | def __parse_osclass(cls, osclass_data):
method __parse_runstats (line 594) | def __parse_runstats(cls, scanrunstats_data):
method __format_element (line 618) | def __format_element(elt_data):
method __format_attributes (line 645) | def __format_attributes(elt_data):
class NmapParserException (line 674) | class NmapParserException(Exception):
method __init__ (line 675) | def __init__(self, msg):
method __str__ (line 678) | def __str__(self):
FILE: libnmap/plugins/backend_host.py
class NmapSqlPlugin (line 16) | class NmapSqlPlugin(NmapBackendPlugin):
class Reports (line 46) | class Reports(Base):
method __init__ (line 61) | def __init__(self, obj_NmapReport):
method decode (line 78) | def decode(self):
method __init__ (line 84) | def __init__(self, **kwargs):
method insert (line 119) | def insert(self, nmap_report):
method get (line 136) | def get(self, report_id=None):
method getall (line 152) | def getall(self):
method delete (line 167) | def delete(self, report_id=None):
FILE: libnmap/plugins/backend_service.py
class NmapSqlPlugin (line 17) | class NmapSqlPlugin(NmapBackendPlugin):
class Reports (line 47) | class Reports(Base):
method __init__ (line 68) | def __init__(self, obj_NmapReport):
method decode (line 88) | def decode(self):
method __init__ (line 94) | def __init__(self, **kwargs):
method insert (line 129) | def insert(self, nmap_report):
method get (line 146) | def get(self, report_id=None):
method getall (line 162) | def getall(self):
method delete (line 177) | def delete(self, report_id=None):
FILE: libnmap/plugins/backendplugin.py
class NmapBackendPlugin (line 4) | class NmapBackendPlugin(object):
method __init__ (line 9) | def __init__(self):
method insert (line 13) | def insert(self, NmapReport):
method delete (line 23) | def delete(self, id):
method get (line 30) | def get(self, id):
method getall (line 38) | def getall(self, filter):
FILE: libnmap/plugins/backendpluginFactory.py
class BackendPluginFactory (line 6) | class BackendPluginFactory(object):
method create (line 13) | def create(cls, plugin_name="mongodb", **kwargs):
FILE: libnmap/plugins/es.py
class NmapElasticsearchPlugin (line 10) | class NmapElasticsearchPlugin(NmapBackendPlugin):
method __init__ (line 15) | def __init__(self, index=None):
method insert (line 22) | def insert(self, report, doc_type=None):
method delete (line 40) | def delete(self, id):
method get (line 47) | def get(self, id):
method getall (line 58) | def getall(self, filter=None):
FILE: libnmap/plugins/mongodb.py
class NmapMongodbPlugin (line 11) | class NmapMongodbPlugin(NmapBackendPlugin):
method __init__ (line 20) | def __init__(self, dbname=None, store=None, **kwargs):
method insert (line 29) | def insert(self, report):
method get (line 42) | def get(self, str_report_id=None):
method getall (line 63) | def getall(self, dict_filter=None):
method delete (line 76) | def delete(self, report_id=None):
FILE: libnmap/plugins/s3.py
class NmapS3Plugin (line 25) | class NmapS3Plugin(NmapBackendPlugin):
method __init__ (line 29) | def __init__(self, **kwargs):
method insert (line 74) | def insert(self, report):
method get (line 94) | def get(self, str_report_id=None):
method getall (line 113) | def getall(self, dict_filter=None):
method delete (line 127) | def delete(self, report_id=None):
FILE: libnmap/plugins/sql.py
class NmapSqlPlugin (line 16) | class NmapSqlPlugin(NmapBackendPlugin):
class Reports (line 46) | class Reports(Base):
method __init__ (line 59) | def __init__(self, obj_NmapReport):
method decode (line 70) | def decode(self):
method __init__ (line 76) | def __init__(self, **kwargs):
method insert (line 111) | def insert(self, nmap_report):
method get (line 128) | def get(self, report_id=None):
method getall (line 144) | def getall(self):
method delete (line 159) | def delete(self, report_id=None):
FILE: libnmap/process.py
class NmapTask (line 23) | class NmapTask(object):
method __init__ (line 34) | def __init__(self, name, starttime=0, extrainfo=''):
class NmapProcess (line 47) | class NmapProcess(Thread):
method __init__ (line 56) | def __init__(self, targets="127.0.0.1",
method _run_init (line 127) | def _run_init(self):
method _whereis (line 145) | def _whereis(self, program):
method get_command_line (line 163) | def get_command_line(self):
method sudo_run (line 176) | def sudo_run(self, run_as='root'):
method sudo_run_background (line 208) | def sudo_run_background(self, run_as='root'):
method run (line 238) | def run(self):
method run_background (line 338) | def run_background(self):
method is_running (line 346) | def is_running(self):
method has_terminated (line 354) | def has_terminated(self):
method has_failed (line 363) | def has_failed(self):
method is_successful (line 371) | def is_successful(self):
method stop (line 379) | def stop(self):
method __process_event (line 390) | def __process_event(self, eventdata):
method command (line 465) | def command(self):
method targets (line 475) | def targets(self):
method options (line 484) | def options(self):
method state (line 493) | def state(self):
method starttime (line 508) | def starttime(self):
method endtime (line 517) | def endtime(self):
method elapsed (line 528) | def elapsed(self):
method summary (line 539) | def summary(self):
method tasks (line 550) | def tasks(self):
method version (line 559) | def version(self):
method current_task (line 569) | def current_task(self):
method etc (line 581) | def etc(self):
method progress (line 593) | def progress(self):
method rc (line 605) | def rc(self):
method stdout (line 614) | def stdout(self):
method stderr (line 624) | def stderr(self):
function main (line 634) | def main():
FILE: libnmap/reportjson.py
class ReportEncoder (line 11) | class ReportEncoder(json.JSONEncoder):
method default (line 12) | def default(self, obj):
class ReportDecoder (line 27) | class ReportDecoder(json.JSONDecoder):
method decode (line 28) | def decode(self, json_str):
FILE: run.py
function start_nmap_dispath (line 10) | def start_nmap_dispath(targets, taskid=None):
FILE: tasks.py
function nmap_dispath (line 44) | def nmap_dispath(targets, taskid=None):
function hydra_dispath (line 56) | def hydra_dispath(targets, protocol, userdic, passdic, taskid=None):
function medusa_dispath (line 72) | def medusa_dispath(targets, protocol, userdic, passdic, taskid=None):
FILE: wyfunc.py
function ip2num (line 6) | def ip2num(ip):
function num2ip (line 10) | def num2ip(num):
function gen_ips (line 18) | def gen_ips(start, end):
function make_ips_c_block (line 23) | def make_ips_c_block(ipaddr):
function ip_check (line 32) | def ip_check(ip):
function make_target_list (line 37) | def make_target_list(targets):
FILE: wyportmap.py
function do_nmap_scan (line 25) | def do_nmap_scan(targets, options=global_options):
function parse_nmap_report (line 68) | def parse_nmap_report(nmap_stdout, taskid=None):
function run_wyportmap (line 100) | def run_wyportmap(targets, taskid=None):
Condensed preview — 28 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (180K chars).
[
{
"path": "ElasticsearchPlugin.py",
"chars": 552,
"preview": "#!/usr/bin/env python\n\nfrom libnmap.parser import NmapParser\nfrom libnmap.reportjson import ReportDecoder\nfrom libnmap.p"
},
{
"path": "README.md",
"chars": 5224,
"preview": "# thorns\nthorns_project 分布式异步队列系统\n\n运行流程\n-----------------------------------\n* 启动redis内存服务器,作为队列存储数据库使用\n* 配置芹菜(celery)运行环"
},
{
"path": "libnmap/__init__.py",
"chars": 234,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = 'Ronald Bister, Mike Boutillier'\n__credits__ = ['Ronald Bister', 'Mike Boutillier'"
},
{
"path": "libnmap/diff.py",
"chars": 2896,
"preview": "# -*- coding: utf-8 -*-\n\n\nclass DictDiffer(object):\n \"\"\"\n Calculate the difference between two dictionaries as"
},
{
"path": "libnmap/objects/__init__.py",
"chars": 214,
"preview": "# -*- coding: utf-8 -*-\n\nfrom libnmap.objects.report import NmapReport\nfrom libnmap.objects.host import NmapHost\nfrom li"
},
{
"path": "libnmap/objects/cpe.py",
"chars": 2190,
"preview": "# -*- coding: utf-8 -*-\n\n\nclass CPE(object):\n \"\"\"\n CPE class offers an API for basic CPE objects.\n Thes"
},
{
"path": "libnmap/objects/host.py",
"chars": 14335,
"preview": "# -*- coding: utf-8 -*-\n\nfrom libnmap.diff import NmapDiff\nfrom libnmap.objects.os import NmapOSFingerprint\n\n\nclass Nmap"
},
{
"path": "libnmap/objects/os.py",
"chars": 12807,
"preview": "# -*- coding: utf-8 -*-\n\nimport warnings\nfrom libnmap.objects.cpe import CPE\n\n\nclass OSFPPortUsed(object):\n \"\"\"\n "
},
{
"path": "libnmap/objects/report.py",
"chars": 10284,
"preview": "# -*- coding: utf-8 -*-\nfrom libnmap.diff import NmapDiff\n\n\nclass NmapReport(object):\n \"\"\"\n NmapReport is the "
},
{
"path": "libnmap/objects/service.py",
"chars": 9861,
"preview": "# -*- coding: utf-8 -*-\nfrom libnmap.diff import NmapDiff\nfrom libnmap.objects.os import CPE\n\n\nclass NmapService(object)"
},
{
"path": "libnmap/parser.py",
"chars": 25674,
"preview": "# -*- coding: utf-8 -*-\n\n\ntry:\n import xml.etree.cElementTree as ET\nexcept ImportError:\n import xml.etree.ElementT"
},
{
"path": "libnmap/plugins/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "libnmap/plugins/backend_host.py",
"chars": 6335,
"preview": "#!/usr/bin/env python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy.schema import Column\nfrom sqlalchemy.types im"
},
{
"path": "libnmap/plugins/backend_service.py",
"chars": 7065,
"preview": "#!/usr/bin/env python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy.schema import Column\nfrom sqlalchemy.types im"
},
{
"path": "libnmap/plugins/backendplugin.py",
"chars": 1147,
"preview": "#!/usr/bin/env python\n\n\nclass NmapBackendPlugin(object):\n \"\"\"\n Abstract class showing to the minimal implement"
},
{
"path": "libnmap/plugins/backendpluginFactory.py",
"chars": 1356,
"preview": "#!/usr/bin/env python\nimport sys\nimport inspect\n\n\nclass BackendPluginFactory(object):\n \"\"\"\n This is a backend "
},
{
"path": "libnmap/plugins/es.py",
"chars": 2071,
"preview": "# -*- coding: utf-8 -*-\n\nimport json\nfrom libnmap.reportjson import ReportEncoder\nfrom libnmap.plugins.backendplugin imp"
},
{
"path": "libnmap/plugins/mongodb.py",
"chars": 3085,
"preview": "#!/usr/bin/env python\nimport json\nfrom pymongo import MongoClient\nfrom bson.objectid import ObjectId\n\nfrom libnmap.repor"
},
{
"path": "libnmap/plugins/s3.py",
"chars": 4795,
"preview": "#!/usr/bin/env python\n\"\"\"\n:mod:`libnmap.plugin.s3` -- S3 Backend Plugin\n=============================================\n\n."
},
{
"path": "libnmap/plugins/sql.py",
"chars": 6102,
"preview": "#!/usr/bin/env python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy.schema import Column\nfrom sqlalchemy.types im"
},
{
"path": "libnmap/process.py",
"chars": 23314,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport os\nimport pwd\nimport shlex\nimport subprocess\nimport multiprocessin"
},
{
"path": "libnmap/reportjson.py",
"chars": 1054,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport json\nfrom libnmap.objects import NmapHost, NmapService, NmapReport"
},
{
"path": "run.py",
"chars": 724,
"preview": "#!/usr/bin/env python\n# encoding: utf-8\n# tasks.py\n# email: ringzero@0x557.org\n\nimport sys\nfrom tasks import *\nfrom wyfu"
},
{
"path": "src/supervisord_client.conf",
"chars": 8933,
"preview": "; Sample supervisor config file.\n;\n; For more information on the config file, please see:\n; http://supervisord.org/confi"
},
{
"path": "src/supervisord_server.conf",
"chars": 9213,
"preview": "; Sample supervisor config file.\n;\n; For more information on the config file, please see:\n; http://supervisord.org/confi"
},
{
"path": "tasks.py",
"chars": 2659,
"preview": "#!/usr/bin/env python\n# encoding: utf-8\n# tasks.py\n# email: ringzero@0x557.org\n\n'''\n\tThorns Project 分布式任务控制脚本\n\ttasks\n\t\t-"
},
{
"path": "wyfunc.py",
"chars": 3514,
"preview": "#!/usr/bin/env python\n# encoding: utf-8\n# wyfunc.py\n# email: ringzero@0x557.org\n\ndef ip2num(ip):\n\tip = [int(x) for x in "
},
{
"path": "wyportmap.py",
"chars": 3122,
"preview": "#!/usr/bin/env python\n# encoding: utf-8\n# mail: ringzero@0x557.org\n\nimport json\nimport sys\nfrom time import sleep\nfrom l"
}
]
About this extraction
This page contains the full source code of the ring04h/thorns GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 28 files (164.8 KB), approximately 39.9k tokens, and a symbol index with 285 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.