class Nmap(object): """ This nmap class allows us to use the nmap port scanner tool from within python by calling nmap3.Nmap() """ def __init__(self, path=None): """ Module initialization :param path: Path where nmap is installed on a user system. On linux system it's typically on /usr/bin/nmap. """ self.nmaptool = path if path else get_nmap_path() self.default_args = "{nmap} {outarg} - " self.maxport = 65389 self.target = "" self.top_ports = dict() self.parser = NmapCommandParser(None) self.raw_ouput = None self.as_root = False def require_root(self, required=True): """ Call this method to add "sudo" in front of nmap call """ self.as_root = required def default_command(self): """ Returns the default nmap command that will be chained with all others eg nmap -oX - """ if self.as_root: return self.default_command_privileged() return self.default_args.format(nmap=self.nmaptool, outarg="-oX") def default_command_privileged(self): """ Commands that require root privileges """ if OS_TYPE == 'win32': # Elevate privileges and return nmap command # For windows now is not fully supported so just return the default return self.default_command() else: return self.default_args.format(nmap="sudo "+self.nmaptool, outarg="-oX") def nmap_version(self): """ Returns nmap version and build details """ # nmap version output is not available in XML format (eg. -oX -) output = self.run_command([self.nmaptool, '--version']) version_data = {} for line in output.splitlines(): if line.startswith('Nmap version '): version_string = line.split(' ')[2] version_data['nmap'] = tuple([int(_) for _ in version_string.split('.')]) elif line.startswith('Compiled with:'): compiled_with = line.split(':')[1].strip() version_data['compiled_with'] = tuple(compiled_with.split(' ')) elif line.startswith('Compiled without:'): compiled_without = line.split(':')[1].strip() version_data['compiled_without'] = tuple(compiled_without.split(' ')) elif line.startswith('Available nsock engines:'): nsock_engines = line.split(':')[1].strip() version_data['nsock_engines'] = tuple(nsock_engines.split(' ')) return version_data # Unique method for repetitive tasks - Use of 'target' variable instead of 'host' or 'subnet' - no need to make difference between 2 strings that are used for the same purpose def scan_command(self, target, arg, args): self.target == target command_args = "{target} {default}".format(target=target, default=arg) scan_command = self.default_command() + command_args if(args): scan_command += " {0}".format(args) scan_shlex = shlex.split(scan_command) output = self.run_command(scan_shlex) xml_root = self.get_xml_et(output) return xml_root def scan_top_ports(self, target, default=10, args=None): """ Perform nmap's top ports scan :param: target can be IP or domain :param: default is the default top port This top port requires root previledges """ if(default > self.maxport): raise ValueError("Port can not be greater than default 65389") self.target = target if(args): assert(isinstance(args, str)), "Expected string got {0} instead".format(type(args)) top_port_args = " {target} --top-ports {default}".format(target=target, default=default) scan_command = self.default_command() + top_port_args if(args): scan_command += " {0}".format(args) scan_shlex = shlex.split(scan_command) # Run the command and get the output output = self.run_command(scan_shlex) if not output: # Probaby and error was raise raise ValueError("Unable to perform requested command") # Begin parsing the xml response xml_root = self.get_xml_et(output) self.top_ports = self.parser.filter_top_ports(xml_root) return self.top_ports def nmap_dns_brute_script(self, target, dns_brute="--script dns-brute.nse"): """ Perform nmap scan using the dns-brute script :param: target can be IP or domain :param: default is the default top port nmap -oX - nmmapper.com --script dns-brute.nse """ self.target = target dns_brute_args = "{target} {default}".format(target=target, default=dns_brute) dns_brute_command = self.default_command() + dns_brute_args dns_brute_shlex = shlex.split(dns_brute_command) # prepare it for popen # Run the command and get the output output = self.run_command(dns_brute_shlex) # Begin parsing the xml response xml_root = self.get_xml_et(output) subdomains = self.parser.filter_subdomains(xml_root) return subdomains def nmap_version_detection(self, target, arg="-sV", args=None): """ Perform nmap scan using the dns-brute script :param: target can be IP or domain nmap -oX - nmmapper.com --script dns-brute.nse """ xml_root = self.scan_command(target=target, arg=arg, args=args) services = self.parser.version_parser(xml_root) return services # Using of basic options for stealth scan def nmap_stealth_scan(self, target, arg="-Pn -sZ", args=None): """ nmap -oX - nmmapper.com -Pn -sZ """ xml_root = self.scan_command(target=target, arg=arg, args=args) self.top_ports = self.parser.filter_stealth_scan(xml_root) return self.top_ports def nmap_detect_firewall(self, target, arg="-sA", args=None): # requires root """ nmap -oX - nmmapper.com -sA @ TODO """ xml_root = self.scan_command(target=target, arg=arg, args=args) # TODO def nmap_os_detection(self, target, arg="-O", args=None): # requires root """ nmap -oX - nmmapper.com -O NOTE: Requires root """ xml_root = self.scan_command(target=target, arg=arg, args=args) os_identified = self.parser.os_identifier_parser(xml_root) return os_identified def nmap_subnet_scan(self, target, arg="-p-", args=None): # requires root """ nmap -oX - nmmapper.com -p- NOTE: Requires root """ xml_root = self.scan_command(target=target, arg=arg, args=args) subnet_discovered = self.parser.parse_nmap_subnetscan(xml_root) return subnet_discovered def nmap_list_scan(self, target, arg="-sL", args=None): # requires root """ The list scan is a degenerate form of target discovery that simply lists each target of the network(s) specified, without sending any packets to the target targets. NOTE: /usr/bin/nmap -oX - 192.168.178.1/24 -sL """ self.target = target xml_root = self.scan_command(target=target, arg=arg, args=args) hosts_discovered = self.parser.parse_nmap_listscan(xml_root) return hosts_discovered def run_command(self, cmd): """ Runs the nmap command using popen @param: cmd--> the command we want run eg /usr/bin/nmap -oX - nmmapper.com --top-ports 10 """ if (os.path.exists(self.nmaptool)): sub_proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) try: output, errs = sub_proc.communicate() except Exception as e: sub_proc.kill() raise (e) else: if 0 != sub_proc.returncode: raise NmapExecutionError('Error during command: "' + ' '.join(cmd) + '"\n\n' + errs.decode('utf8')) # Response is bytes so decode the output and return return output.decode('utf8').strip() else: raise NmapNotInstalledError() def get_xml_et(self, command_output): """ @ return xml ET """ try: self.raw_ouput = command_output return ET.fromstring(command_output) except ParseError: raise NmapXMLParserError()
class Nmap(object): """ This nmap class allows us to use the nmap port scanner tool from within python by calling nmap3.Nmap() """ def __init__(self, path=None): """ Module initialization :param path: Path where nmap is installed on a user system. On linux system it's typically on /usr/bin/nmap. """ self.nmaptool = path if path else get_nmap_path() self.default_args = "{nmap} {outarg} - " self.maxport = 65389 self.target = "" self.top_ports = dict() self.parser = NmapCommandParser(None) def default_command(self): """ Returns the default nmap command that will be chained with all others eg nmap -oX - """ return self.default_args.format(nmap=self.nmaptool, outarg="-oX") # Unique method for repetitive tasks - Use of 'target' variable instead of 'host' or 'subnet' - no need to make difference between 2 strings that are used for the same purpose def scan_command(self, target, arg, args): self.target == target command_args = "{target} {default}".format(target=target, default=arg) scan_command = self.default_command() + command_args if (args): scan_command += " {0}".format(args) scan_shlex = shlex.split(scan_command) output = self.run_command(scan_shlex) xml_root = self.get_xml_et(output) return xml_root def scan_top_ports(self, target, default=10, args=None): """ Perform nmap's top ports scan :param: target can be IP or domain :param: default is the default top port This top port requires root previledges """ parser = NmapCommandParser(None) if (default > self.maxport): raise ValueError("Port can not be greater than default 65389") self.target = target if (args): assert (isinstance(args, str)), "Expected string got {0} instead".format( type(args)) top_port_args = " {target} --top-ports {default}".format( target=target, default=default) scan_command = self.default_command() + top_port_args if (args): scan_command += " {0}".format(args) scan_shlex = shlex.split(scan_command) # Run the command and get the output output = self.run_command(scan_shlex) if not output: # Probaby and error was raise raise ValueError("Unable to perform requested command") # Begin parsing the xml response xml_root = self.get_xml_et(output) self.top_ports = parser.filter_top_ports(xml_root) return self.top_ports def nmap_dns_brute_script(self, target, dns_brute="--script dns-brute.nse"): """ Perform nmap scan using the dns-brute script :param: target can be IP or domain :param: default is the default top port nmap -oX - nmmapper.com --script dns-brute.nse """ self.target = target parser = NmapCommandParser(None) dns_brute_args = "{target} {default}".format(target=target, default=dns_brute) dns_brute_command = self.default_command() + dns_brute_args dns_brute_shlex = shlex.split( dns_brute_command) # prepare it for popen # Run the command and get the output output = self.run_command(dns_brute_shlex) # Begin parsing the xml response xml_root = self.get_xml_et(output) subdomains = parser.filter_subdomains(xml_root) return subdomains def nmap_version_detection(self, target, arg="-sV", args=None): """ Perform nmap scan using the dns-brute script :param: target can be IP or domain nmap -oX - nmmapper.com --script dns-brute.nse """ xml_root = self.scan_command(target=target, arg=arg, args=args) services = self.parser.version_parser(xml_root) return services # Using of basic options for stealth scan def nmap_stealth_scan(self, target, arg="-Pn -sZ", args=None): """ nmap -oX - nmmapper.com -Pn -sZ """ xml_root = self.scan_command(target=target, arg=arg, args=args) self.top_ports = self.parser.filter_stealth_scan(xml_root) return self.top_ports def nmap_detect_firewall(self, target, arg="-sA", args=None): # requires root """ nmap -oX - nmmapper.com -sA @ TODO """ xml_root = self.scan_command(target=target, arg=arg, args=args) # TODO def nmap_os_detection(self, target, arg="-O", args=None): # requires root """ nmap -oX - nmmapper.com -O NOTE: Requires root """ xml_root = self.scan_command(target=target, arg=arg, args=args) os_identified = self.parser.os_identifier_parser(xml_root) return os_identified def nmap_subnet_scan(self, target, arg="-p-", args=None): # requires root """ nmap -oX - nmmapper.com -p- NOTE: Requires root """ xml_root = self.scan_command(target=target, arg=arg, args=args) subnet_discovered = self.parser.parse_nmap_subnetscan(xml_root) return subnet_discovered def nmap_list_scan(self, target, arg="-sL", args=None): # requires root """ The list scan is a degenerate form of target discovery that simply lists each target of the network(s) specified, without sending any packets to the target targets. NOTE: /usr/bin/nmap -oX - 192.168.178.1/24 -sL """ self.target = target xml_root = self.scan_command(target=target, arg=arg, args=args) hosts_discovered = self.parser.parse_nmap_listscan(xml_root) return hosts_discovered def run_command(self, cmd): """ Runs the nmap command using popen @param: cmd--> the command we want run eg /usr/bin/nmap -oX - nmmapper.com --top-ports 10 """ if (os.path.exists(self.nmaptool)): sub_proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) try: output, errs = sub_proc.communicate() except Exception as e: sub_proc.kill() raise (e) else: # Response is bytes so decode the output and return return output.decode('utf8').strip() else: raise NmapNotInstalledError() def get_xml_et(self, command_output): """ @ return xml ET """ try: return ET.fromstring(command_output) except xml.etree.ElementTree.ParseError: raise NmapXMLParserError()