diff --git a/faraday_client/bin/hosts.py b/faraday_client/bin/hosts.py new file mode 100644 index 00000000..ce61b5c3 --- /dev/null +++ b/faraday_client/bin/hosts.py @@ -0,0 +1,215 @@ +""" +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information +""" +from past.builtins import cmp + +import functools + +from netaddr import IPNetwork, IPRange +from os import path +from tempfile import NamedTemporaryFile +from re import match +from socket import inet_aton +from struct import unpack + +from faraday_client.persistence.server import models +from faraday_client.config.configuration import getInstanceConfiguration + +__description__ = 'Show hosts similar to `hosts` metasploit' +__prettyname__ = 'Show hosts' + +# FIXME Update when persistence API changes +COLUMNS = { + 'host': lambda host, workspace: host.name, + 'mac': lambda host, workspace: host.mac, + 'hostname': lambda host, workspace: ','.join(host.getHostnames()), + 'os': lambda host, workspace: host.os, + 'description': lambda host, workspace: host.description, + 'vulns': lambda host, workspace: str(host.vuln_amount), + 'owned': lambda host, workspace: 'owned' if host.owned else '' +} + +CONF = getInstanceConfiguration() + +def get_default_columns(): + try: + with open(path.join(CONF.getConfigPath(),'columns-hosts.txt'), 'r') as f: + columns = f.read() + except: + columns = "host,mac,hostname,os,description" + return columns + +def set_default_columns(columns): + with open(path.join(CONF.getConfigPath(),'columns-hosts.txt'), 'w') as f: + f.write(columns) + +def create_host(models, workspace, ip): + columns = { + "name" : ip, + } + host = models.Host(columns, workspace) + models.create_host(workspace, host) + +def delete_host(workspace, host): + models.delete_host(workspace, host.id) + +def change_host(workspace, host, what): + for (key,val) in what.items(): + if val.startswith('@') and path.isfile(val[1:]): + with open(val[1:]) as f: + val = f.read() + if key == 'hostname': + host.hostnames.append(val) + elif key == 'hostnames': + host.hostnames = val.split(',') + else: + setattr(host, key, val) + models.update_host(workspace, host, None) + +def dump(host): + for attr,val in host.__dict__.items(): + print(attr + ": " + str(val)) + +def parse_host(host): + hosts = [] + if path.isfile(host): + with open(host) as f: + for host in f: + hosts += parse_host(host.split()[0]) + else: + if host.find('/') != -1: + for ip in IPNetwork(host): + hosts.append(str(ip)) + elif host.find('-') != -1: + host_from,host_to = host.split('-') + for ip in IPRange(host_from.strip(), host_to.strip()): + hosts.append(str(ip)) + else: + hosts.append(host) + return hosts + +def cast(value): + value = value.strip() + if value.isdigit(): + return int(value) + elif match("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", value): + return unpack("!I", inet_aton(value) )[0] + else: + return value + +def main(workspace='', args=None, parser=None): + parser.add_argument('hosts', nargs='*', help='List of Hosts names', default=[]) + parser.add_argument('-a', action='store_true', help='Add new Host', default=False) + parser.add_argument('-d', action='store_true', help='Delete Host', default=False) + parser.add_argument('-e', action='append', metavar='key=value', help='Edit Host', type=lambda kv: kv.split("=")) + parser.add_argument('-S', type=str, metavar='text', help='Search Hosts', default='') + parser.add_argument('-c', metavar='columns', help='Comma separated list of columns to show.', + default=get_default_columns()) + parser.add_argument('-C', metavar='columns', help='Set default comma separated list of columns to show.', + default='') + parser.add_argument('-s', type=str, metavar='column', help='Sort order by column name or column number', default='host') + parser.add_argument('-r', action='store_true', help='Reverse sort ordering', default=False) + parser.add_argument('-R', action='store_true', help='Save IP\'s to file', default=False) + parser.add_argument('-o', type=str, metavar='file', help='Save columns to file', default='') + + parser.add_argument('--dump', action='store_true', help='Dump all available fields', default=False) + + parser.add_argument('--options', action='store_true', help='Show options', default=False) + parser.add_argument('--usage', action='store_true', help='Show usage', default=False) + + parsed_args = parser.parse_args(args) + if parsed_args.options: + print(parser.format_help()) + return 0, None + if parsed_args.usage: + print(parser.format_usage()) + return 0, None + + ip_list = [] + for host in parsed_args.hosts: + ip_list += parse_host(host) + search = parsed_args.S + + if parsed_args.C: + columns = list(filter(None, parsed_args.C.split(','))) + set_default_columns(parsed_args.C) + elif parsed_args.c: + columns = list(filter(None, parsed_args.c.split(','))) + sort_col = columns.index(parsed_args.s) if COLUMNS.get(parsed_args.s) else int(parsed_args.s)-1 + + if parsed_args.R: + tmp = NamedTemporaryFile(mode="w", delete=False) + hosts_to_export = set() + + added = 0 + if parsed_args.a: + for ip in ip_list: + create_host(models, workspace, ip=ip) + added += 1 + if added: + print("added %d hosts"%added) + return 0, None + + lines = [] + deleted = 0 + changed = 0 + hosts = models.get_hosts(workspace) + for host in hosts: + #import ipdb;ipdb.set_trace() + if not ip_list or host.name in ip_list: + if not search or list(filter(lambda h:h.lower().find(search.lower())!=-1, host.getHostnames()))\ + or host.os.lower().find(search.lower())!=-1 or host.description.lower().find(search.lower())!=-1\ + or ('owned' if host.owned else '').find(search.lower())!=-1: + if parsed_args.d: + delete_host(workspace, host) + deleted += 1 + elif parsed_args.e: + change_host(workspace, host, dict(parsed_args.e)) + changed += 1 + elif parsed_args.dump: + dump(host) + else: + column_data = [] + + for column in columns: + column_data += [COLUMNS[column](host, workspace)] + + lines += [column_data] + + if parsed_args.R and not host.getName() in hosts_to_export: + tmp.write("{host}\n".format(host=host.getName())) + hosts_to_export.add(host.getName()) + if deleted: + print("deleted %d hosts"%deleted) + elif changed: + print("changed %d hosts"%changed) + + if not lines: + return 0, None + + col_widths = {i:len(columns[i]) for i in range(len(columns))} + for row in lines: + for i in range(len(row)): + col_widths[i] = len(row[i]) if len(row[i]) >= col_widths[i] else col_widths[i] + + sorting = lambda l1,l2: cmp(cast(l1[sort_col]), cast(l2[sort_col])) + sorting_reverse = lambda l1,l2: cmp(cast(l2[sort_col]), cast(l1[sort_col])) + + print("".join(columns[i].ljust(col_widths[i]+2) for i in range(len(columns)))) + print("".join(("-"*len(columns[i])).ljust(col_widths[i]+2) for i in range(len(columns)))) + for row in sorted(lines, key=functools.cmp_to_key(sorting_reverse if parsed_args.r else sorting)): + print("".join(row[i].ljust(col_widths[i]+2) for i in range(len(row)))) + + if parsed_args.R: + print(tmp.name) + if parsed_args.o: + with open(parsed_args.o, 'w') as o: + for row in sorted(lines, key=functools.cmp_to_key(sorting_reverse if parsed_args.r else sorting)): + o.write(",".join(word for word in row) + '\n') + + return 0, None + + +# I'm Py3 diff --git a/faraday_client/bin/nets.py b/faraday_client/bin/nets.py new file mode 100644 index 00000000..256ccd9d --- /dev/null +++ b/faraday_client/bin/nets.py @@ -0,0 +1,219 @@ +""" +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information +""" +from past.builtins import cmp + +import functools + +from netaddr import IPNetwork, IPRange +from os import path +from tempfile import NamedTemporaryFile +from re import match +from socket import inet_aton +from struct import unpack + +from faraday_client.persistence.server import models +from faraday_client.config.configuration import getInstanceConfiguration + +__description__ = 'Show networks' +__prettyname__ = 'Show networks' + +# FIXME Update when persistence API changes +COLUMNS = { + 'netblock': lambda netblock, workspace: str(netblock.range), + 'hosts': lambda netblock, workspace: str(netblock.hosts), + 'vulns': lambda netblock, workspace: str(netblock.vulns), + 'owneds': lambda netblock, workspace: str(netblock.owneds) +} + +CONF = getInstanceConfiguration() + +class Netblock: + def __init__(self, netblock): + if netblock.find('/') != -1: + self.range = IPNetwork(netblock) + elif netblock.find('-') != -1: + host_from,host_to = netblock.split('-') + self.range = IPRange(host_from.strip(), host_to.strip()) + else: + self.range = IPNetwork(netblock) + self.hosts = 0 + self.vulns = 0 + self.owneds = 0 + +def get_default_columns(): + try: + with open(path.join(CONF.getConfigPath(),'columns-networks.txt'), 'r') as f: + columns = f.read() + except: + columns = "netblock,hosts,vulns,owneds" + return columns + +def set_default_columns(columns): + with open(path.join(CONF.getConfigPath(),'columns-networks.txt'), 'w') as f: + f.write(columns) + +def get_netblock(ip): + for netblock in netblocks: + if ip in netblock.range: + return netblock + +def add_netblock(net): + global netblocks + netblock = Netblock(net) + netblocks.append(netblock) + return netblock + +def parse_netbloks(netblock): + netblocks = [] + if path.isfile(netblock): + with open(netblock) as f: + for netblock in f: + netblocks += parse_netbloks(netblock.split()[0]) + else: + netblocks.append(Netblock(netblock)) + return netblocks + +hosts = [] +def select(host, vulns): + global hosts + netblock = get_netblock(host.name) + if not netblock: + netblock = add_netblock(f"{host.name}/{netsize}") + netblock.hosts += 1 if not host.name in hosts else 0 + netblock.vulns += vulns + netblock.owneds += 1 if host.owned else 0 + if not host.name in hosts: + hosts.append(host.name) + +def cast(value): + value = value.strip() + if value.isdigit(): + return int(value) + elif match("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", value): + return unpack("!I", inet_aton(value) )[0] + else: + return value + +netblocks = [] +netsize = None +def main(workspace='', args=None, parser=None): + global netblocks, netsize + parser.add_argument('netblocks', nargs='*', help='List of Netblocks', default=[]) + parser.add_argument('-n', type=int, metavar='netsize', help='CIDR size', default=24) + parser.add_argument('-p', type=str, metavar='ports', help='List of ports comma separated to filter', default="") + parser.add_argument('-Sh', type=str, metavar='text', help='Search Hosts', default='') + parser.add_argument('-Ss', type=str, metavar='text', help='Search Services', default='') + parser.add_argument('-Sv', type=str, metavar='text', help='Search Vulnerabilities', default='') + parser.add_argument('--severity', metavar='text', help='Comma separated list of columns to show.', + default="") + + parser.add_argument('-c', metavar='columns', help='Comma separated list of columns to show.', + default=get_default_columns()) + parser.add_argument('-C', metavar='columns', help='Set default comma separated list of columns to show.', + default='') + parser.add_argument('-s', type=str, metavar='column', help='Sort order by column name or column number', default='netblock') + parser.add_argument('-r', action='store_true', help='Reverse sort ordering', default=False) + parser.add_argument('-R', action='store_true', help='Save IP\'s to file', default=False) + parser.add_argument('-o', type=str, metavar='file', help='Save columns to file', default='') + + parser.add_argument('--dump', action='store_true', help='Dump all available fields', default=False) + + parser.add_argument('--options', action='store_true', help='Show help', default=False) + parser.add_argument('--usage', action='store_true', help='Show help', default=False) + + parsed_args = parser.parse_args(args) + if parsed_args.options: + print(parser.format_help()) + return 0, None + if parsed_args.usage: + print(parser.format_usage()) + return 0, None + + for netblock in parsed_args.netblocks: + netblocks += parse_netbloks(netblock) + netsize = parsed_args.n + search_host = parsed_args.Sh + search_service = parsed_args.Ss + search_vuln = parsed_args.Sv + port_list = list( map(int, filter(lambda p:p, parsed_args.p.split(',')) ) ) + + if parsed_args.C: + columns = list(filter(None, parsed_args.C.split(','))) + set_default_columns(parsed_args.C) + elif parsed_args.c: + columns = list(filter(None, parsed_args.c.split(','))) + sort_col = columns.index(parsed_args.s) if COLUMNS.get(parsed_args.s) else int(parsed_args.s)-1 + + if parsed_args.R: + tmp = NamedTemporaryFile(mode="w", delete=False) + hosts_to_export = set() + + hosts = models.get_hosts(workspace) + for host in hosts: + #import ipdb;ipdb.set_trace() + if (not search_host or list(filter(lambda h:h.lower().find(search_host.lower())!=-1, host.getHostnames()))\ + or host.os.lower().find(search_host.lower())!=-1 or host.description.lower().find(search_host.lower())!=-1\ + or ('owned' if host.owned else '').find(search_host.lower())!=-1 )\ + and not port_list and not search_service and not search_vuln and not parsed_args.severity: + select(host, vulns=host.getVulnsAmount()) + elif port_list or search_service or search_vuln or parsed_args.severity: + for service in host.getServices(): + if search_service and service.getName() in search_service: + select(host, vulns=service.getVulnsAmount()) + continue + elif port_list or search_vuln or parsed_args.severity: + if port_list and (set(port_list) & set(service.getPorts())): + select(host, vulns=service.getVulnsAmount()) + continue + elif search_vuln or parsed_args.severity: + for vuln in service.getVulns(): + if search_vuln and (vuln.getDescription().lower().find(search_vuln.lower()) != -1 or vuln.getName().lower().find(search_vuln.lower()) != -1): + select(host, vulns=1) + continue + if vuln.severity in parsed_args.severity: + select(host, vulns=1) + continue + + lines = [] + for netblock in netblocks: + column_data = [] + + for column in columns: + column_data += [COLUMNS[column](netblock, workspace)] + + lines += [column_data] + + if parsed_args.R and not host.getName() in hosts_to_export: + tmp.write("{host}\n".format(host=host.getName())) + hosts_to_export.add(host.getName()) + + if not lines: + return 0, None + + col_widths = {i:len(columns[i]) for i in range(len(columns))} + for row in lines: + for i in range(len(row)): + col_widths[i] = len(row[i]) if len(row[i]) >= col_widths[i] else col_widths[i] + + sorting = lambda l1,l2: cmp(cast(l1[sort_col]), cast(l2[sort_col])) + sorting_reverse = lambda l1,l2: cmp(cast(l2[sort_col]), cast(l1[sort_col])) + + print("".join(columns[i].ljust(col_widths[i]+2) for i in range(len(columns)))) + print("".join(("-"*len(columns[i])).ljust(col_widths[i]+2) for i in range(len(columns)))) + for row in sorted(lines, key=functools.cmp_to_key(sorting_reverse if parsed_args.r else sorting)): + print("".join(row[i].ljust(col_widths[i]+2) for i in range(len(row)))) + + if parsed_args.R: + print(tmp.name) + if parsed_args.o: + with open(parsed_args.o, 'w') as o: + for row in sorted(lines, key=functools.cmp_to_key(sorting_reverse if parsed_args.r else sorting)): + o.write(",".join(word for word in row) + '\n') + + return 0, None + + +# I'm Py3 diff --git a/faraday_client/bin/notes.py b/faraday_client/bin/notes.py new file mode 100644 index 00000000..f7aad5c9 --- /dev/null +++ b/faraday_client/bin/notes.py @@ -0,0 +1,252 @@ +from past.builtins import cmp + +import functools + +from netaddr import IPNetwork, IPRange +from os import path +from tempfile import NamedTemporaryFile +from colorama import Fore +from re import match, compile +from socket import inet_aton +from struct import unpack + +from faraday_client.model.common import factory +from faraday_client.persistence.server import models +from faraday_client.config.configuration import getInstanceConfiguration + +__description__ = 'Show info similar to `notes` metasploit' +__prettyname__ = 'Show info' + +# FIXME Update when persistence API changes +COLUMNS = { + 'host': lambda search, vuln, service, workspace: models.get_host(workspace, service.getParent()).name, + 'service': lambda search, vuln, service, workspace: service.name, + 'port': lambda search, vuln, service, workspace: ','.join(map(str, service.ports)), + 'protocol': lambda search, vuln, service, workspace: service.protocol, + 'status': lambda search, vuln, service, workspace: service.status, + 'version': lambda search, vuln, service, workspace: service.version, + 'name': lambda search, vuln, service, workspace: vuln.name, + 'description': lambda search, vuln, service, workspace: grep(vuln.description, search), +} + +CONF = getInstanceConfiguration() + +def get_default_columns(): + try: + with open(path.join(CONF.getConfigPath(),'columns-notes.txt'), 'r') as f: + columns = f.read() + except: + columns = "host,port,service,name,description" + return columns + +def set_default_columns(columns): + with open(path.join(CONF.getConfigPath(),'columns-notes.txt'), 'w') as f: + f.write(columns) + +def create_info(models, workspace, service, vuln): + for (key,val) in vuln.items(): + if val.startswith('@') and path.isfile(val[1:]): + with open(val[1:]) as f: + val = f.read() + vuln[key] = val + try: + models.create_vuln(workspace, factory.createModelObject(models.Vuln.class_signature, + vuln['name'], + workspace, + ref=vuln.get('reference',''), + severity='info', + resolution=vuln.get('resolution',''), + confirmed=False, + desc=vuln.get('description',''), + parent_id=service.getID(), + parent_type='Service' + )) + except ConflictInDatabase as ex: + if ex.answer.status_code == 409: + try: + old_id = ex.answer.json()['object']['_id'] + except KeyError: + print("Vulnerability already exists. Couldn't fetch ID") + return 2, None + else: + print("A vulnerability with ID %s already exists!" % old_id) + return 2, None + else: + print("Unknown error while creating the vulnerability") + return 2, None + except CantCommunicateWithServerError as ex: + print("Error while creating vulnerability:", ex.response.text) + return 2, None + +def delete_info(workspace, vuln): + models.delete_vuln(workspace, vuln.id) + +def change_info(workspace, vuln, what): + for (key,val) in what.items(): + if val.startswith('@') and path.isfile(val[1:]): + with open(val[1:]) as f: + val = f.read() + setattr(vuln, key, val) + models.update_vuln(workspace, vuln) + +def dump(vuln): + for attr,val in vuln.__dict__.items(): + print(attr + ": " + str(val)) + +def grep(haystack, needle): + result = '' + if needle.lower() in haystack.lower() != -1: + for line in haystack.split('\n'): + if needle.lower() in line.lower(): + result += line + '\n' if result else line + else: + result = haystack + return result + +def parse_host(host): + hosts = [] + if path.isfile(host): + with open(host) as f: + for host in f: + hosts += parse_host(host.split()[0]) + else: + if host.find('/') != -1: + for ip in IPNetwork(host): + hosts.append(str(ip)) + elif host.find('-') != -1: + host_from,host_to = host.split('-') + for ip in IPRange(host_from.strip(), host_to.strip()): + hosts.append(str(ip)) + else: + hosts.append(host) + return hosts + +def cast(value): + value = value.strip() + if value.isdigit(): + return int(value) + elif match("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", value): + return unpack("!I", inet_aton(value) )[0] + else: + return value + +def main(workspace='', args=None, parser=None): + parser.add_argument('hosts', nargs="*", help='Info text', default=[]) + parser.add_argument('-p', type=str, metavar='ports', help='List of ports comma separated to filter', default="") + parser.add_argument('--protocol', type=str, metavar='protocol', help='Only this protocol', default='') + parser.add_argument('--up', action='store_true', help='Only open ports', default=False) + parser.add_argument('-a', metavar='key=value', help='Add new Info', type=lambda opt: opt.split(",")) + parser.add_argument('-d', action='store_true', help='Delete Info', default=False) + parser.add_argument('-e', action='append', metavar='key=value', help='Edit Info', type=lambda kv: kv.split("=")) + parser.add_argument('-S', type=str, metavar='text', help='Search Info', default="") + + parser.add_argument('-c', metavar='columns', help='Comma separated list of columns to show.', + default=get_default_columns()) + parser.add_argument('-C', metavar='columns', help='Set default comma separated list of columns to show.', + default='') + parser.add_argument('-s', type=str, metavar='column', help='Sort order by column name or column number', default='host') + parser.add_argument('-r', action='store_true', help='Reverse sort ordering', default=False) + parser.add_argument('-R', action='store_true', help='Save IP\'s to file', default=False) + parser.add_argument('-o', type=str, metavar='file', help='Save columns to file', default='') + + parser.add_argument('--dump', action='store_true', help='Dump all available fields', default=False) + + parser.add_argument('--options', action='store_true', help='Show options', default=False) + parser.add_argument('--usage', action='store_true', help='Show usage', default=False) + + parsed_args = parser.parse_args(args) + if parsed_args.options: + print(parser.format_help()) + return 0, None + if parsed_args.usage: + print(parser.format_usage()) + return 0, None + + ip_list = [] + for host in parsed_args.hosts: + ip_list += parse_host(host) + port_list = list( map(int, filter(lambda p:p, parsed_args.p.split(',')) ) ) + + protocol = parsed_args.protocol + search = parsed_args.S + + if parsed_args.C: + columns = list(filter(None, parsed_args.C.split(','))) + set_default_columns(parsed_args.C) + elif parsed_args.c: + columns = list(filter(None, parsed_args.c.split(','))) + + sort_col = columns.index(parsed_args.s) if COLUMNS.get(parsed_args.s) else int(parsed_args.s)-1 + + port_list = list( map(int, filter(lambda p:p, parsed_args.p.split(',')) ) ) + if parsed_args.R: + tmp = NamedTemporaryFile(mode="w", delete=False) + hosts_to_export = set() + + lines = [] + added = 0 + deleted = 0 + changed = 0 + hosts = models.get_hosts(workspace) + for host in hosts: + #import ipdb; ipdb.set_trace() + if not ip_list or host.getName() in ip_list: + for service in host.getServices(): + if not port_list or set(port_list) & set(service.getPorts()): + if parsed_args.a: + create_info(models, workspace, service, dict([x.split('=') for x in parsed_args.a])) + added += 1 + else: + for vuln in service.getVulns(): + if vuln.severity == 'info': + if (not search or vuln.getDescription().lower().find(search.lower()) != -1 or vuln.getName().lower().find(search.lower()) != -1): + if parsed_args.d: + delete_info(workspace, vuln) + deleted += 1 + elif parsed_args.e: + change_info(workspace, vuln, dict(parsed_args.e)) + changed += 1 + elif parsed_args.dump: + dump(vuln) + else: + column_data = [] + + for column in columns: + column_data += [COLUMNS[column](search, vuln, service, workspace)] + + lines += [column_data] + if parsed_args.R and not host.getName() in hosts_to_export: + tmp.write("{host}\n".format(host=host.getName())) + hosts_to_export.add(host.getName()) + if added: + print("added %d info"%added) + elif deleted: + print("deleted %d info"%deleted) + elif changed: + print("changed %d info"%changed) + + if not lines: + return 0, None + + col_widths = {i:len(columns[i]) for i in range(len(columns))} + for row in lines: + for i in range(len(row)): + col_widths[i] = len(row[i]) if len(row[i]) >= col_widths[i] else col_widths[i] + + sorting = lambda l1,l2: cmp(cast(l1[sort_col]), cast(l2[sort_col])) + sorting_reverse = lambda l1,l2: cmp(cast(l2[sort_col]), cast(l1[sort_col])) + + print("".join(columns[i].ljust(col_widths[i]+2) for i in range(len(columns)))) + print("".join(("-"*len(columns[i])).ljust(col_widths[i]+2) for i in range(len(columns)))) + for row in sorted(lines, key=functools.cmp_to_key(sorting_reverse if parsed_args.r else sorting)): + print("".join(row[i].ljust(col_widths[i]+2) for i in range(len(row)))) + + if parsed_args.R: + print(tmp.name) + if parsed_args.o: + with open(parsed_args.o, 'w') as o: + for row in sorted(lines, key=functools.cmp_to_key(sorting_reverse if parsed_args.r else sorting)): + o.write(",".join(word for word in row) + '\n') + + return 0, None + \ No newline at end of file diff --git a/faraday_client/bin/services.py b/faraday_client/bin/services.py new file mode 100644 index 00000000..344ff205 --- /dev/null +++ b/faraday_client/bin/services.py @@ -0,0 +1,222 @@ +""" +Faraday Penetration Test IDE +Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) +See the file 'doc/LICENSE' for the license information +""" +from past.builtins import cmp + +import functools + +from netaddr import IPNetwork, IPRange +from os import path +from tempfile import NamedTemporaryFile +from re import match +from socket import inet_aton +from struct import unpack + +from faraday_client.persistence.server import models +from faraday_client.config.configuration import getInstanceConfiguration + +__description__ = 'Show services similar to `services` metasploit' +__prettyname__ = 'Show services' + +# FIXME Update when persistence API changes +COLUMNS = { + 'host': lambda service, workspace: models.get_host(workspace, service.getParent()).name, + 'service': lambda service, workspace: service.name, + 'port': lambda service, workspace: ','.join(map(str, service.ports)), + 'protocol': lambda service, workspace: service.protocol, + 'status': lambda service, workspace: service.status, + 'version': lambda service, workspace: service.version, + 'vulns': lambda service, workspace: service.vuln_amount +} + +CONF = getInstanceConfiguration() + +def get_default_columns(): + try: + with open(path.join(CONF.getConfigPath(),'columns-services.txt'), 'r') as f: + columns = f.read() + except: + columns = "host,port,protocol,service,status,version" + return columns + +def set_default_columns(columns): + with open(path.join(CONF.getConfigPath(),'columns-services.txt'), 'w') as f: + f.write(columns) + +def create_service(models, workspace, host, port, protocol): + columns = { + "parent": host.id, + "ports": [port], + "protocol": protocol, + "status": "open", + "version": "", + } + service = models.Service(columns, workspace) + models.create_service(workspace, service) + +def delete_service(workspace, service): + models.delete_service(workspace, service.id) + +def change_service(workspace, service, what): + for (key,val) in what.items(): + if val.startswith('@') and path.isfile(val[1:]): + with open(val[1:]) as f: + val = f.read() + setattr(service, key, val) + models.update_service(workspace, service, None) + +def dump(service): + for attr,val in service.__dict__.items(): + print(attr + ": " + str(val)) + +def parse_host(host): + hosts = [] + if path.isfile(host): + with open(host) as f: + for host in f: + hosts += parse_host(host.split()[0]) + else: + if host.find('/') != -1: + for ip in IPNetwork(host): + hosts.append(str(ip)) + elif host.find('-') != -1: + host_from,host_to = host.split('-') + for ip in IPRange(host_from.strip(), host_to.strip()): + hosts.append(str(ip)) + else: + hosts.append(host) + return hosts + +def cast(value): + value = value.strip() + if value.isdigit(): + return int(value) + elif match("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", value): + return unpack("!I", inet_aton(value) )[0] + else: + return value + +def main(workspace='', args=None, parser=None): + parser.add_argument('hosts', nargs='*', help='List of Hosts names', default=[]) + parser.add_argument('-p', type=str, metavar='ports', help='List of ports comma separated to filter', default="") + parser.add_argument('--protocol', type=str, metavar='protocol', help='Only this protocol', default='') + parser.add_argument('--up', action='store_true', help='Only open ports', default=False) + parser.add_argument('-a', action='store_true', help='Add new Service', default=False) + parser.add_argument('-d', action='store_true', help='Delete Service', default=False) + parser.add_argument('-e', action='append', metavar='key=value', help='Edit Service', type=lambda kv: kv.split("=")) + parser.add_argument('-S', type=str, metavar='text', help='Search Services', default=[]) + + parser.add_argument('-c', metavar='columns', help='Comma separated list of columns to show.', + default=get_default_columns()) + parser.add_argument('-C', metavar='columns', help='Set default comma separated list of columns to show.', + default='') + parser.add_argument('-s', type=str, metavar='column', help='Sort order by column name or column number', default='host') + parser.add_argument('-r', action='store_true', help='Reverse sort ordering', default=False) + parser.add_argument('-R', action='store_true', help='Save IP\'s to file', default=False) + parser.add_argument('-o', type=str, metavar='file', help='Save columns to file', default='') + + parser.add_argument('--dump', action='store_true', help='Dump all available fields', default=False) + + parser.add_argument('--options', action='store_true', help='Show options', default=False) + parser.add_argument('--usage', action='store_true', help='Show usage', default=False) + + parsed_args = parser.parse_args(args) + if parsed_args.options: + print(parser.format_help()) + return 0, None + if parsed_args.usage: + print(parser.format_usage()) + return 0, None + + ip_list = [] + for host in parsed_args.hosts: + ip_list += parse_host(host) + port_list = list( map(int, filter(lambda p:p, parsed_args.p.split(',')) ) ) + protocol = parsed_args.protocol + search = parsed_args.S + + if parsed_args.C: + columns = list(filter(None, parsed_args.C.split(','))) + set_default_columns(parsed_args.C) + elif parsed_args.c: + columns = list(filter(None, parsed_args.c.split(','))) + + sort_col = columns.index(parsed_args.s) if COLUMNS.get(parsed_args.s) else int(parsed_args.s)-1 + + if parsed_args.R: + tmp = NamedTemporaryFile(mode="w", delete=False) + hosts_to_export = set() + + lines = [] + added = 0 + deleted = 0 + changed = 0 + hosts = models.get_hosts(workspace) + for host in hosts: + #import ipdb;ipdb.set_trace() + if not ip_list or host.getName() in ip_list: + if parsed_args.a: + for port in port_list: + create_service(models, workspace, host, port=port, protocol=protocol if protocol else 'tcp') + added += 1 + else: + for service in host.getServices(): + if (not search or search.lower() in service.getName().lower() or search.lower() in service.getVersion().lower())\ + and (not protocol or service.protocol == protocol)\ + and (not parsed_args.up or service.status == 'open'): + for port in service.getPorts(): + if not port_list or port in port_list: + if parsed_args.d: + delete_service(workspace, service) + deleted += 1 + elif parsed_args.e: + change_service(workspace, service, dict(parsed_args.e)) + changed += 1 + elif parsed_args.dump: + dump(service) + else: + column_data = [] + + for column in columns: + column_data += [COLUMNS[column](service, workspace)] + + lines += [column_data] + + if parsed_args.R and not host.getName() in hosts_to_export: + tmp.write("{host}\n".format(host=host.getName())) + hosts_to_export.add(host.getName()) + if added: + print("added %d services"%added) + elif deleted: + print("deleted %d services"%deleted) + elif changed: + print("changed %d services"%changed) + + if not lines: + return 0, None + + col_widths = {i:len(columns[i]) for i in range(len(columns))} + for row in lines: + for i in range(len(row)): + col_widths[i] = len(row[i]) if len(row[i]) >= col_widths[i] else col_widths[i] + + sorting = lambda l1,l2: cmp(cast(l1[sort_col]), cast(l2[sort_col])) + sorting_reverse = lambda l1,l2: cmp(cast(l2[sort_col]), cast(l1[sort_col])) + + print("".join(columns[i].ljust(col_widths[i]+2) for i in range(len(columns)))) + print("".join(("-"*len(columns[i])).ljust(col_widths[i]+2) for i in range(len(columns)))) + for row in sorted(lines, key=functools.cmp_to_key(sorting_reverse if parsed_args.r else sorting)): + print("".join(row[i].ljust(col_widths[i]+2) for i in range(len(row)))) + + if parsed_args.R: + print(tmp.name) + if parsed_args.o: + with open(parsed_args.o, 'w') as o: + for row in sorted(lines, key=functools.cmp_to_key(sorting_reverse if parsed_args.r else sorting)): + o.write(",".join(word for word in row) + '\n') + + return 0, None + +# I'm Py3 diff --git a/faraday_client/bin/vulns.py b/faraday_client/bin/vulns.py new file mode 100644 index 00000000..9fde8b40 --- /dev/null +++ b/faraday_client/bin/vulns.py @@ -0,0 +1,274 @@ +from past.builtins import cmp + +import functools + +from netaddr import IPNetwork, IPRange +from os import path +from tempfile import NamedTemporaryFile +from colorama import Fore +from re import match, compile +from socket import inet_aton +from struct import unpack + +from faraday_client.model.common import factory +from faraday_client.persistence.server import models +from faraday_client.config.configuration import getInstanceConfiguration + +__description__ = 'Show vulnerabilities similar to `vulns` metasploit' +__prettyname__ = 'Show vulnerabilities' + +# FIXME Update when persistence API changes +COLUMNS = { + 'host': lambda search, vuln, service, workspace: models.get_host(workspace, service.getParent()).name, + 'service': lambda search, vuln, service, workspace: service.name, + 'port': lambda search, vuln, service, workspace: ','.join(map(str, service.ports)), + 'protocol': lambda search, vuln, service, workspace: service.protocol, + 'status': lambda search, vuln, service, workspace: service.status, + 'version': lambda search, vuln, service, workspace: service.version, + 'vulns': lambda search, vuln, service, workspace: service.vuln_amount, + 'severity': lambda search, vuln, service, workspace: vuln.severity, + 'name': lambda search, vuln, service, workspace: color(vuln.severity) + vuln.name + Fore.RESET, + 'description': lambda search, vuln, service, workspace: color(vuln.severity) + grep(vuln.description, search) + Fore.RESET, +} + +CONF = getInstanceConfiguration() + +def get_default_columns(): + try: + with open(path.join(CONF.getConfigPath(),'columns-vulns.txt'), 'r') as f: + columns = f.read() + except: + columns = "host,port,service,severity,name" + return columns + +def set_default_columns(columns): + with open(path.join(CONF.getConfigPath(),'columns-vulns.txt'), 'w') as f: + f.write(columns) + +def color(severity): + if severity == 'critical': + return Fore.RED + elif severity == 'high': + return Fore.LIGHTRED_EX + elif severity == 'med': + return Fore.LIGHTYELLOW_EX + elif severity == 'low': + return Fore.GREEN + elif severity == 'info': + return Fore.CYAN + elif severity == 'unclassified': + return Fore.GREY + +def create_vuln(models, workspace, service, vuln): + for (key,val) in vuln.items(): + if val.startswith('@') and path.isfile(val[1:]): + with open(val[1:]) as f: + val = f.read() + vuln[key] = val + try: + models.create_vuln(workspace, factory.createModelObject(models.Vuln.class_signature, + vuln['name'], + workspace, + ref=vuln.get('reference',''), + severity=vuln.get('severity','info'), + resolution=vuln.get('resolution',''), + confirmed=False, + desc=vuln.get('description',''), + parent_id=service.getID(), + parent_type='Service' + )) + except ConflictInDatabase as ex: + if ex.answer.status_code == 409: + try: + old_id = ex.answer.json()['object']['_id'] + except KeyError: + print("Vulnerability already exists. Couldn't fetch ID") + return 2, None + else: + print("A vulnerability with ID %s already exists!" % old_id) + return 2, None + else: + print("Unknown error while creating the vulnerability") + return 2, None + except CantCommunicateWithServerError as ex: + print("Error while creating vulnerability:", ex.response.text) + return 2, None + +def delete_vuln(workspace, vuln): + models.delete_vuln(workspace, vuln.id) + +def change_vuln(workspace, vuln, what): + for (key,val) in what.items(): + if val.startswith('@') and path.isfile(val[1:]): + with open(val[1:]) as f: + val = f.read() + setattr(vuln, key, val) + models.update_vuln(workspace, vuln) + +def dump(vuln): + for attr,val in vuln.__dict__.items(): + print(attr + ": " + str(val)) + +ansi_escape = compile(r'(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]') +def _len(col): + return len(ansi_escape.sub('', str(col))) + +def grep(haystack, needle): + result = '' + if needle.lower() in haystack.lower() != -1: + for line in haystack.split('\n'): + if needle.lower() in line.lower(): + result += line + '\n' if result else line + else: + result = haystack + return result + +def parse_host(host): + hosts = [] + if path.isfile(host): + with open(host) as f: + for host in f: + hosts += parse_host(host.split()[0]) + else: + if host.find('/') != -1: + for ip in IPNetwork(host): + hosts.append(str(ip)) + elif host.find('-') != -1: + host_from,host_to = host.split('-') + for ip in IPRange(host_from.strip(), host_to.strip()): + hosts.append(str(ip)) + else: + hosts.append(host) + return hosts + +def cast(value): + value = value.strip() + if value.isdigit(): + return int(value) + elif match("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", value): + return unpack("!I", inet_aton(value) )[0] + else: + return value + +def main(workspace='', args=None, parser=None): + parser.add_argument('hosts', nargs="*", help='Vulnerability text', default=[]) + parser.add_argument('-p', type=str, metavar='ports', help='List of ports comma separated to filter', default="") + parser.add_argument('--protocol', type=str, metavar='protocol', help='Only this protocol', default='') + parser.add_argument('--up', action='store_true', help='Only open ports', default=False) + parser.add_argument('-a', metavar='key=value', help='Add new Vulnerability', type=lambda opt: opt.split(",")) + parser.add_argument('-d', action='store_true', help='Delete Vulnerability', default=False) + parser.add_argument('-e', action='append', metavar='key=value', help='Edit Vulnerability', type=lambda kv: kv.split("=")) + parser.add_argument('-S', type=str, metavar='text', help='Search Vulnerabilities', default="") + parser.add_argument('--severity', metavar='text', help='Comma separated list of columns to show.', + default="critical,high,med,low,info,unclassified") + + parser.add_argument('-c', metavar='columns', help='Comma separated list of columns to show.', + default=get_default_columns()) + parser.add_argument('-C', metavar='columns', help='Set default comma separated list of columns to show.', + default='') + parser.add_argument('-s', type=str, metavar='column', help='Sort order by column name or column number', default='host') + parser.add_argument('-r', action='store_true', help='Reverse sort ordering', default=False) + parser.add_argument('-R', action='store_true', help='Save IP\'s to file', default=False) + parser.add_argument('-o', type=str, metavar='file', help='Save columns to file', default='') + + parser.add_argument('--dump', action='store_true', help='Dump all available fields', default=False) + + parser.add_argument('--options', action='store_true', help='Show options', default=False) + parser.add_argument('--usage', action='store_true', help='Show usage', default=False) + + parsed_args = parser.parse_args(args) + if parsed_args.options: + print(parser.format_help()) + return 0, None + if parsed_args.usage: + print(parser.format_usage()) + return 0, None + + ip_list = [] + for host in parsed_args.hosts: + ip_list += parse_host(host) + port_list = list( map(int, filter(lambda p:p, parsed_args.p.split(',')) ) ) + + protocol = parsed_args.protocol + search = parsed_args.S + + if parsed_args.C: + columns = list(filter(None, parsed_args.C.split(','))) + set_default_columns(parsed_args.C) + elif parsed_args.c: + columns = list(filter(None, parsed_args.c.split(','))) + + sort_col = columns.index(parsed_args.s) if COLUMNS.get(parsed_args.s) else int(parsed_args.s)-1 + + port_list = list( map(int, filter(lambda p:p, parsed_args.p.split(',')) ) ) + if parsed_args.R: + tmp = NamedTemporaryFile(mode="w", delete=False) + hosts_to_export = set() + + lines = [] + added = 0 + deleted = 0 + changed = 0 + hosts = models.get_hosts(workspace) + for host in hosts: + #import ipdb; ipdb.set_trace() + if not ip_list or host.getName() in ip_list: + for service in host.getServices(): + if not port_list or set(port_list) & set(service.getPorts()): + if parsed_args.a: + create_vuln(models, workspace, service, dict([x.split('=') for x in parsed_args.a])) + added += 1 + else: + for vuln in service.getVulns(): + if vuln.severity in parsed_args.severity: + if (not search or vuln.getDescription().lower().find(search.lower()) != -1 or vuln.getName().lower().find(search.lower()) != -1): + if parsed_args.d: + delete_vuln(workspace, vuln) + deleted += 1 + elif parsed_args.e: + change_vuln(workspace, vuln, dict(parsed_args.e)) + changed += 1 + elif parsed_args.dump: + dump(vuln) + else: + column_data = [] + + for column in columns: + column_data += [COLUMNS[column](search, vuln, service, workspace)] + + lines += [column_data] + if parsed_args.R and not host.getName() in hosts_to_export: + tmp.write("{host}\n".format(host=host.getName())) + hosts_to_export.add(host.getName()) + if added: + print("added %d vulnerabilities"%added) + elif deleted: + print("deleted %d vulnerabilities"%deleted) + elif changed: + print("changed %d vulnerabilities"%changed) + + if not lines: + return 0, None + + col_widths = {i:len(columns[i]) for i in range(len(columns))} + for row in lines: + for i in range(len(row)): + col_widths[i] = _len(row[i]) if _len(row[i]) >= col_widths[i] else col_widths[i] + + sorting = lambda l1,l2: cmp(cast(l1[sort_col]), cast(l2[sort_col])) + sorting_reverse = lambda l1,l2: cmp(cast(l2[sort_col]), cast(l1[sort_col])) + + print("".join(columns[i].ljust(col_widths[i]+2) for i in range(len(columns)))) + print("".join(("-"*len(columns[i])).ljust(col_widths[i]+2) for i in range(len(columns)))) + for row in sorted(lines, key=functools.cmp_to_key(sorting_reverse if parsed_args.r else sorting)): + print("".join(row[i].ljust(col_widths[i]+2) for i in range(len(row)))) + + if parsed_args.R: + print(tmp.name) + if parsed_args.o: + with open(parsed_args.o, 'w') as o: + for row in sorted(lines, key=functools.cmp_to_key(sorting_reverse if parsed_args.r else sorting)): + o.write(",".join(word for word in row) + '\n') + + return 0, None + \ No newline at end of file