Skip to content

Commit c843334

Browse files
committed
Added support for asynchronous calls in class NmapAsync to those who want to perform asynchronous scanning.
1 parent 46b3463 commit c843334

File tree

2 files changed

+112
-25
lines changed

2 files changed

+112
-25
lines changed

nmap3/nmap3.py

Lines changed: 97 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,18 @@
1919
#
2020
#
2121

22-
import csv
23-
import io
2422
import os
25-
import re
2623
import shlex
2724
import subprocess
2825
import sys
2926
import simplejson as json
3027
import argparse
28+
import asyncio
3129
from xml.etree import ElementTree as ET
3230
from xml.etree.ElementTree import ParseError
33-
from nmapparser import NmapCommandParser
34-
from utils import get_nmap_path, user_is_root
35-
from exceptions import NmapNotInstalledError, NmapXMLParserError, NmapExecutionError
36-
37-
#from nmap3.nmapparser import NmapCommandParser
38-
#from nmap3.utils import get_nmap_path, user_is_root
39-
#from nmap3.exceptions import NmapNotInstalledError, NmapXMLParserError, NmapExecutionError
40-
31+
from nmap3.nmapparser import NmapCommandParser
32+
from nmap3.utils import get_nmap_path, user_is_root
33+
from nmap3.exceptions import NmapNotInstalledError, NmapXMLParserError, NmapExecutionError
4134
import xml
4235

4336
__author__ = 'Wangolo Joel ([email protected])'
@@ -225,6 +218,7 @@ def nmap_os_detection(self, target, arg="-O", args=None): # requires root
225218
nmap -oX - nmmapper.com -O
226219
NOTE: Requires root
227220
"""
221+
print("Performing some scannins")
228222
xml_root = self.scan_command(target=target, arg=arg, args=args)
229223
results = self.parser.os_identifier_parser(xml_root)
230224
return results
@@ -272,7 +266,7 @@ def run_command(self, cmd, timeout=None):
272266
return output.decode('utf8').strip()
273267
else:
274268
raise NmapNotInstalledError()
275-
269+
276270
def get_xml_et(self, command_output):
277271
"""
278272
@ return xml ET
@@ -283,7 +277,6 @@ def get_xml_et(self, command_output):
283277
except ParseError:
284278
raise NmapXMLParserError()
285279

286-
287280
class NmapScanTechniques(Nmap):
288281
"""
289282
Extends Nmap to include nmap commands
@@ -339,27 +332,26 @@ def tpl(i):
339332

340333
return xml_root
341334

335+
@user_is_root
342336
def nmap_fin_scan(self, target, args=None):
343337
"""
344338
Perform scan using nmap's fin scan
345339
346340
@cmd nmap -sF 192.168.178.1
347341
348342
"""
349-
self.require_root()
350-
351343
xml_root = self.scan_command(self.fin_scan, target=target, args=args)
352344
results = self.parser.filter_top_ports(xml_root)
353345
return results
354-
346+
347+
@user_is_root
355348
def nmap_syn_scan(self, target, args=None):
356349
"""
357350
Perform syn scan on this given
358351
target
359352
360353
@cmd nmap -sS 192.168.178.1
361354
"""
362-
self.require_root()
363355
xml_root = self.scan_command(self.sync_scan, target=target, args=args)
364356
results = self.parser.filter_top_ports(xml_root)
365357
return results
@@ -375,14 +367,14 @@ def nmap_tcp_scan(self, target, args=None):
375367
xml_root = self.scan_command(self.tcp_connt, target=target, args=args)
376368
results = self.parser.filter_top_ports(xml_root)
377369
return results
378-
370+
371+
@user_is_root
379372
def nmap_udp_scan(self, target, args=None):
380373
"""
381374
Scan target using the nmap tcp connect
382375
383376
@cmd nmap -sU 192.168.178.1
384377
"""
385-
self.require_root()
386378

387379
if (args):
388380
assert (isinstance(args, str)), "Expected string got {0} instead".format(type(args))
@@ -410,7 +402,6 @@ def nmap_idle_scan(self, target, args=None):
410402
results = self.parser.filter_top_ports(xml_root)
411403
return results
412404

413-
414405
class NmapHostDiscovery(Nmap):
415406
"""
416407
This object will perform host discovery
@@ -501,12 +492,95 @@ def nmap_disable_dns(self, target, args=None):
501492
results = self.parser.filter_top_ports(xml_root)
502493
return results
503494

495+
class NmapAsync(Nmap):
496+
def __init__(self, path=None):
497+
super(NmapAsync, self).__init__(path=path)
498+
self.stdout = asyncio.subprocess.PIPE
499+
self.stderr = asyncio.subprocess.PIPE
500+
501+
async def run_command(self, cmd, timeout=None):
502+
if (os.path.exists(self.nmaptool)):
503+
process = await asyncio.create_subprocess_shell(cmd,stdout=self.stdout,stderr=self.stderr)
504+
505+
try:
506+
data, stderr = await process.communicate()
507+
except Exception as e:
508+
raise (e)
509+
else:
510+
if 0 != process.returncode:
511+
raise NmapExecutionError('Error during command: "' + ' '.join(cmd) + '"\n\n' + errs.decode('utf8'))
512+
513+
# Response is bytes so decode the output and return
514+
return data.decode('utf8').strip()
515+
else:
516+
raise NmapNotInstalledError()
517+
518+
async def scan_command(self, target, arg, args=None, timeout=None):
519+
self.target == target
520+
521+
command_args = "{target} {default}".format(target=target, default=arg)
522+
scancommand = self.default_command() + command_args
523+
if (args):
524+
scancommand += " {0}".format(args)
525+
526+
output = await self.run_command(scancommand, timeout=timeout)
527+
xml_root = self.get_xml_et(output)
504528

529+
return xml_root
530+
531+
async def scan_top_ports(self, target, default=10, args=None, timeout=None):
532+
top_port_args = " {target} --top-ports {default}".format(target=target, default=default)
533+
command = self.default_command() + top_port_args
534+
if (args):
535+
command += " {0}".format(args)
536+
537+
output = await self.run_command(command, timeout=timeout)
538+
if not output:
539+
raise ValueError("Unable to perform requested command")
540+
541+
self.top_ports = self.parser.filter_top_ports(self.get_xml_et(output))
542+
return self.top_ports
543+
544+
async def nmap_dns_brute_script(self, target, dns_brute="--script dns-brute.nse", timeout=None):
545+
self.target = target
546+
547+
dns_brute_args = "{target} {default}".format(target=target, default=dns_brute)
548+
dns_brute_command = self.default_command() + dns_brute_args
549+
550+
# Run the command and get the output
551+
output = await self.run_command(dns_brute_command, timeout=timeout)
552+
subdomains = self.parser.filter_subdomains(self.get_xml_et(output))
553+
return subdomains
554+
555+
async def nmap_version_detection(self, target, arg="-sV", args=None, timeout=None):
556+
xml_root = await self.scan_command(target=target, arg=arg, timeout=timeout)
557+
services = self.parser.filter_top_ports(xml_root)
558+
return services
559+
560+
async def nmap_stealth_scan(self, target, arg="-Pn -sZ", args=None):
561+
xml_root = await self.scan_command(target=target, arg=arg, args=args)
562+
self.top_ports = self.parser.filter_top_ports(xml_root)
563+
return self.top_ports
564+
565+
async def nmap_os_detection(self, target, arg="-O", args=None): # requires root
566+
xml_root = await self.scan_command(target=target, arg=arg, args=args)
567+
results = self.parser.os_identifier_parser(xml_root)
568+
return results
569+
570+
async def nmap_subnet_scan(self, target, arg="-p-", args=None): # requires root
571+
xml_root = await self.scan_command(target=target, arg=arg, args=args)
572+
results = self.parser.filter_top_ports(xml_root)
573+
return results
574+
575+
async def nmap_list_scan(self, target, arg="-sL", args=None): # requires root
576+
xml_root = await self.scan_command(target=target, arg=arg, args=args)
577+
results = self.parser.filter_top_ports(xml_root)
578+
return results
579+
505580
if __name__ == "__main__":
506581
parser = argparse.ArgumentParser(prog="Python3 nmap")
507582
parser.add_argument('-d', '--d', help='Help', required=True)
508583
args = parser.parse_args()
509584

510-
nmap = Nmap()
511-
result = nmap.scan_top_ports(target='127.0.0.1')
512-
print(json.dumps(result, indent=4, sort_keys=True))
585+
nmap = NmapAsync()
586+
asyncio.run(nmap.nmap_version_detection(target='127.0.0.1'))

nmap3/utils.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@
2424
import re
2525
import os
2626
import ctypes
27+
import functools
2728

2829
__author__ = 'Wangolo Joel ([email protected])'
2930
__version__ = '1.4.9'
3031
__last_modification__ = '2019/12/11'
3132

32-
3333
def get_nmap_path():
3434
"""
3535
Returns the location path where nmap is installed
@@ -54,7 +54,6 @@ def get_nmap_path():
5454
else:
5555
return output.decode('utf8').strip()
5656

57-
5857
def get_nmap_version():
5958
nmap = get_nmap_path()
6059
cmd = nmap + " --version"
@@ -82,3 +81,17 @@ def wrapper(*args, **kwargs):
8281
else:
8382
return {"error":True, "msg":"You must be root/administrator to continue!"}
8483
return wrapper
84+
85+
def nmap_is_installed_async():
86+
def wrapper(func):
87+
@functools.wraps(func)
88+
async def wrapped(*args, **kwargs):
89+
nmap_path = get_nmap_path()
90+
91+
if(os.path.exists(nmap_path)):
92+
return await func(*args, **kwargs)
93+
else:
94+
print({"error":True, "msg":"Nmap has not been install on this system yet!"})
95+
return {"error":True, "msg":"Nmap has not been install on this system yet!"}
96+
return wrapped
97+
return wrapper

0 commit comments

Comments
 (0)