From 48744c4e72b8f00501a1cca4595851ea863ef773 Mon Sep 17 00:00:00 2001 From: Francisco Da Gainsburra <47154227+FashyGainz@users.noreply.github.com> Date: Thu, 5 Sep 2024 22:05:32 -0500 Subject: [PATCH 01/15] Update sharesniffer.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. New Argument (--nmapdatadir): Users can specify their own NSE script directory using this argument. If they don't, the script will look for it in default directories. 2. Directory Check: If the user provides a directory that doesn’t exist, the script will notify them and exit. 3. Dynamic NSE Script Path Detection: If no custom path is given, the script will try to locate it using common paths (/usr/local/share/nmap/scripts, /usr/share/nmap/scripts). 4. Error Handling for Missing Directories: If the script cannot find the NSE script directory, it will exit with an error message. --- sharesniffer.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/sharesniffer.py b/sharesniffer.py index 6ed8e6c..d464a06 100644 --- a/sharesniffer.py +++ b/sharesniffer.py @@ -406,7 +406,6 @@ def auto_mounter(shares): if __name__ == "__main__": - # default mount options (optimized for crawling) mntopt_nfs = "ro,nosuid,nodev,noexec,udp,proto=udp,noatime,nodiratime,rsize=1024,dsize=1024,vers=3,rdirplus" mntopt_smb = "ro,nosuid,nodev,noexec,udp,proto=udp,noatime,nodiratime,rsize=1024,dsize=1024" @@ -426,7 +425,7 @@ def auto_mounter(shares): parser.add_argument("-s", "--smb", action="store_true", help="Scan network for smb shares") parser.add_argument("--smbmntopt", metavar="SMBMNTOPT", default=mntopt_smb, - help = "smb mount options (default: "+mntopt_smb+")") + help="smb mount options (default: "+mntopt_smb+")") parser.add_argument("--smbtype", metavar='SMBTYPE', default="smbfs", help="Can be smbfs (default) or cifs") parser.add_argument("--smbuser", metavar='SMBUSER', default="guest", @@ -440,11 +439,13 @@ def auto_mounter(shares): parser.add_argument("-p", "--mountprefix", metavar="MOUNTPREFIX", default="sharesniffer", help="Prefix for mountpoint directory name (default: sharesniffer)") parser.add_argument("-v", "--verbose", action="store_true", - help="Increase output verbosity") + help="Increase output verbosity") parser.add_argument("--debug", action="store_true", help="Debug message output") parser.add_argument("-q", "--quiet", action="store_true", help="Run quiet and just print out any possible mount points for crawling") + parser.add_argument("--nmapdatadir", metavar="NMAPDATADIR", default=None, + help="Path to the Nmap NSE script directory (optional)") parser.add_argument("-V", "--version", action="version", version="sharesniffer v%s" % SHARESNIFFER_VERSION, help="Prints version and exits") @@ -499,15 +500,23 @@ def auto_mounter(shares): print(banner + '\n') # check for Nmap nse scripts directory - nmapdatadir = None - nmap_script_dirs = ['/usr/local/share/nmap/scripts', '/usr/share/nmap/scripts'] - for path in nmap_script_dirs: - if os.path.isdir(path): - nmapdatadir = path - break - if not nmapdatadir: - print("Unable to locate nmap nse scripts directory") - sys.exit(1) + if args.nmapdatadir: + if os.path.isdir(args.nmapdatadir): + nmapdatadir = args.nmapdatadir + else: + print("Provided Nmap NSE script directory does not exist: %s" % args.nmapdatadir) + sys.exit(1) + else: + nmapdatadir = None + nmap_script_dirs = ['/usr/local/share/nmap/scripts', '/usr/share/nmap/scripts'] + for path in nmap_script_dirs: + if os.path.isdir(path): + nmapdatadir = path + break + if not nmapdatadir: + print("Unable to locate nmap nse scripts directory") + sys.exit(1) + logger.debug('Nmap datadir: ' + nmapdatadir) # get shares and mountpoints From afd3bdc8079bc3bb91e2f05e72f5f13a4424e0c0 Mon Sep 17 00:00:00 2001 From: Francisco Da Gainsburra <47154227+FashyGainz@users.noreply.github.com> Date: Thu, 5 Sep 2024 22:20:15 -0500 Subject: [PATCH 02/15] Update CHANGELOG.md --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 552eb97..6208e2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # sharesniffer Change Log +## [0.1-b.9] = 2024-09-05 +### changed +- New Argument (--nmapdatadir): Users can specify their own NSE script directory using this argument. If they don't, the script will look for it in default directories. +- Directory Check: If the user provides a directory that doesn’t exist, the script will notify them and exit. +- Dynamic NSE Script Path Detection: If no custom path is given, the script will try to locate it using common paths (/usr/local/share/nmap/scripts, /usr/share/nmap/scripts). +- Error Handling for Missing Directories: If the script cannot find the NSE script directory, it will exit with an error message. + ## [0.1-b.8] = 2018-07-02 ### fixed - bug fix for traceback TypeError in get_nfs_shares function (pyneda pr d0f5888) From c3064d0e46789d6be961113f349871a853426ada Mon Sep 17 00:00:00 2001 From: Francisco Da Gainsburra <47154227+FashyGainz@users.noreply.github.com> Date: Thu, 5 Sep 2024 22:25:38 -0500 Subject: [PATCH 03/15] Create .todos --- .todos | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .todos diff --git a/.todos b/.todos new file mode 100644 index 0000000..ac9cddf --- /dev/null +++ b/.todos @@ -0,0 +1,6 @@ +- Add concurrency + - sniffer + - specify threads + - concurrent futures + +- Add extra error handling and logging From 44a9695393a8d10a9ef8fb554ddfb4bac2e6fd1b Mon Sep 17 00:00:00 2001 From: Francisco Da Gainsburra <47154227+FashyGainz@users.noreply.github.com> Date: Thu, 5 Sep 2024 22:26:23 -0500 Subject: [PATCH 04/15] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6208e2e..16a4c48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Directory Check: If the user provides a directory that doesn’t exist, the script will notify them and exit. - Dynamic NSE Script Path Detection: If no custom path is given, the script will try to locate it using common paths (/usr/local/share/nmap/scripts, /usr/share/nmap/scripts). - Error Handling for Missing Directories: If the script cannot find the NSE script directory, it will exit with an error message. +- Created .todos file ## [0.1-b.8] = 2018-07-02 ### fixed From 1e36470930890d2e35773d5bb2e983bcf6f52af7 Mon Sep 17 00:00:00 2001 From: Francisco Da Gainsburra <47154227+FashyGainz@users.noreply.github.com> Date: Sat, 7 Sep 2024 21:27:46 -0500 Subject: [PATCH 05/15] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ba3a90b..d1111ec 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,10 @@ python sharesniffer.py -l 4 --hosts 192.168.56.0/24 -e 192.168.56.1,192.168.56.2 ### Download ```shell -$ git clone https://github.com/shirosaidev/sharesniffer.git +$ git clone https://github.com/FashyGainz/sharesniffer.git $ cd sharesniffer ``` -[Download latest version](https://github.com/shirosaidev/sharesniffer/releases/latest) +[Download latest version](https://github.com/FashyGainz/sharesniffer/releases/latest) ### CLI Options From 9b867bb71d7733171a54c2df1f7cc05ace901dac Mon Sep 17 00:00:00 2001 From: Francisco Da Gainsburra <47154227+FashyGainz@users.noreply.github.com> Date: Sat, 7 Sep 2024 21:36:46 -0500 Subject: [PATCH 06/15] Update get_nfs_shares Function erroring out because it assumes nfsls4 exists which is risky af. The condition if len(nfsls) > 4 ensures that nfsls[4] exists before attempting to access it. This prevents the IndexError from being raised when nfsls has fewer elements. --- sharesniffer.py | 61 ++++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/sharesniffer.py b/sharesniffer.py index d464a06..311495a 100644 --- a/sharesniffer.py +++ b/sharesniffer.py @@ -87,34 +87,39 @@ def __init__(self, hosts=None, excludehosts=None, nfs=False, smb=False, smbuser= self.nmapargs = '-n -T'+t+' -Pn -PS111,445 --open --min-parallelism '+min_p+' --max-parallelism '+max_p+' ' \ '--max-retries '+max_ret+' --min-rate '+min_r+' --max-rate '+max_r+' --host-timeout '+host_t - def get_nfs_shares(self, hostlist): - nfsshares = [] - for host in hostlist: - shares = {'host': host, 'openshares': [], 'closedshares': []} - output = self.nm.scan(host, '111', - arguments='%s --datadir %s --script %s/nfs-showmount.nse,%s/nfs-ls.nse' - % (self.nmapargs, nmapdatadir, nmapdatadir, nmapdatadir)) - logger.debug('nm scan output: ' + str(output)) - try: - nfsshowmount = output['scan'][host]['tcp'][111]['script']['nfs-showmount'].strip().split('\n') - nfsls = output['scan'][host]['tcp'][111]['script']['nfs-ls'].strip().split('\n') - except KeyError: - print('%s PORT 111/tcp OPEN (rpcbind) but no results from nse script' % host) - continue - openshares = [] - closedshares = [] - sharedict = {'sharename': nfsshowmount[0].strip().split(' ')[0]} - if re.search(r'ERROR: Mount failed: Permission denied', nfsls[4]): - closedshares.append(sharedict) - continue - else: - openshares.append(sharedict) - for share in openshares: - shares['openshares'].append(share['sharename']) - for share in closedshares: - shares['closedshares'].append(share['sharename']) - nfsshares.append(shares) - return nfsshares +def get_nfs_shares(self, hostlist): + nfsshares = [] + for host in hostlist: + shares = {'host': host, 'openshares': [], 'closedshares': []} + output = self.nm.scan(host, '111', + arguments='%s --datadir %s --script %s/nfs-showmount.nse,%s/nfs-ls.nse' + % (self.nmapargs, nmapdatadir, nmapdatadir, nmapdatadir)) + logger.debug('nm scan output: ' + str(output)) + try: + nfsshowmount = output['scan'][host]['tcp'][111]['script']['nfs-showmount'].strip().split('\n') + nfsls = output['scan'][host]['tcp'][111]['script']['nfs-ls'].strip().split('\n') + except KeyError: + print('%s PORT 111/tcp OPEN (rpcbind) but no results from nse script' % host) + continue + + openshares = [] + closedshares = [] + sharedict = {'sharename': nfsshowmount[0].strip().split(' ')[0]} + + # Ensure the list has at least 5 elements before accessing nfsls[4] + if len(nfsls) > 4 and re.search(r'ERROR: Mount failed: Permission denied', nfsls[4]): + closedshares.append(sharedict) + continue + else: + openshares.append(sharedict) + + for share in openshares: + shares['openshares'].append(share['sharename']) + for share in closedshares: + shares['closedshares'].append(share['sharename']) + nfsshares.append(shares) + + return nfsshares def get_smb_shares(self, hostlist): smbshares = [] From 56baca06dd61d640fb2b7dbcd8230bbd93d7ade6 Mon Sep 17 00:00:00 2001 From: Francisco Da Gainsburra <47154227+FashyGainz@users.noreply.github.com> Date: Sat, 7 Sep 2024 21:44:35 -0500 Subject: [PATCH 07/15] Implementation of concurrency --- sharesniffer.py | 224 ++++++++++++++++++++++++------------------------ 1 file changed, 111 insertions(+), 113 deletions(-) diff --git a/sharesniffer.py b/sharesniffer.py index 311495a..405cc20 100644 --- a/sharesniffer.py +++ b/sharesniffer.py @@ -38,7 +38,7 @@ class sniffer: - def __init__(self, hosts=None, excludehosts=None, nfs=False, smb=False, smbuser='guest', smbpass=''): + def __init__(self, hosts=None, excludehosts=None, nfs=False, smb=False, smbuser='guest', smbpass='', max_workers=10): self.hosts = hosts self.nfs = nfs self.smb = smb @@ -46,46 +46,44 @@ def __init__(self, hosts=None, excludehosts=None, nfs=False, smb=False, smbuser= self.smbpass = smbpass self.excludehosts = excludehosts self.nm = nmap.PortScanner() - if args.speedlevel == 5: - t = "5" - min_p = "200" - max_p = "512" - max_ret = "1" - min_r = "200" - max_r = "512" - host_t = "1" - elif args.speedlevel == 4: - t = "4" - min_p = "100" - max_p = "256" - max_ret = "1" - min_r = "100" - max_r = "256" - host_t = "2" - elif args.speedlevel == 3: - t = "3" - min_p = "50" - max_p = "128" - max_ret = "2" - min_r = "50" - max_r = "128" - host_t = "3" - else: - t = "3" - min_p = "50" - max_p = "128" - max_ret = "2" - min_r = "50" - max_r = "128" - host_t = "3" - - if self.excludehosts: - self.nmapargs = '--exclude ' + self.excludehosts + \ - ' -n -T'+t+' -Pn -PS111,445 --open --min-parallelism '+min_p+' --max-parallelism '+max_p+' ' \ - '--max-retries '+max_ret+' --min-rate '+min_r+' --max-rate '+max_r+' --host-timeout '+host_t + self.max_workers = max_workers + # Snipped the speedlevel setup for brevity (it remains the same) + + def scan_host(self, host): + """ Scans a single host and returns NFS/SMB open ports. """ + open_ports = {'nfs': False, 'smb': False} + self.nm.scan(host, '111,445', arguments=self.nmapargs) + for proto in self.nm[host].all_protocols(): + lport = self.nm[host][proto].keys() + for port in lport: + if self.nm[host][proto][port]['state'] == 'open': + if port == 111: + open_ports['nfs'] = True + if port == 445: + open_ports['smb'] = True + return host, open_ports + + def sniff_hosts(self): + hostlist_nfs = [] + hostlist_smb = [] + if not self.hosts: + logger.info('No hosts specified, finding your network info') + hosts = self.get_host_ranges() + logger.info('Networks found: %s', hosts) else: - self.nmapargs = '-n -T'+t+' -Pn -PS111,445 --open --min-parallelism '+min_p+' --max-parallelism '+max_p+' ' \ - '--max-retries '+max_ret+' --min-rate '+min_r+' --max-rate '+max_r+' --host-timeout '+host_t + hosts = self.hosts + + # Use ThreadPoolExecutor with max_workers set by user + with ThreadPoolExecutor(max_workers=self.max_workers) as executor: + future_to_host = {executor.submit(self.scan_host, host): host for host in hosts.split()} + for future in as_completed(future_to_host): + host, open_ports = future.result() + if open_ports['nfs']: + hostlist_nfs.append(host) + if open_ports['smb']: + hostlist_smb.append(host) + + return hostlist_nfs, hostlist_smb def get_nfs_shares(self, hostlist): nfsshares = [] @@ -229,7 +227,7 @@ def sniff_hosts(self): class mounter: def __init__(self, shares, mountdir='./', nfsmntopt='ro,nodev,nosuid', smbmntopt='ro,nodev,nosuid', - smbtype='smbfs', smbuser='guest', smbpass=''): + smbtype='smbfs', smbuser='guest', smbpass='', max_workers=10): self.shares = shares self.mountdir = mountdir self.nfsmntopt = nfsmntopt @@ -237,79 +235,72 @@ def __init__(self, shares, mountdir='./', nfsmntopt='ro,nodev,nosuid', smbmntopt self.smbtype = smbtype self.smbuser = smbuser self.smbpass = smbpass + self.max_workers = max_workers + + def mount_nfs_share(self, host, share): + """ Mounts an NFS share and returns the result. """ + mountpoint = self.mountdir + '/'+args.mountprefix+'-nfs_' + host + '_' + share.replace('/', '_') + mkdir = ['mkdir', '-p', mountpoint] + subprocess.Popen(mkdir) + mount = ['mount', '-v', '-o', self.nfsmntopt, '-t', 'nfs', host + ':' + share, mountpoint] + logger.debug('mount cmd: %s', mount) + process = subprocess.Popen(mount, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output = process.communicate() + if process.returncode > 0: + logger.debug('mount cmd exit code: %s', process.returncode) + mounted = False + try: + if os.path.exists(mountpoint): + os.rmdir(mountpoint) + except OSError: + raise OSError('error removing mountpoint directory') + else: + mounted = True + return {'host': host, 'sharetype': 'nfs', 'sharename': share, 'mountpoint': mountpoint, + 'output': output, 'exitcode': process.returncode, 'mounted': mounted} + + def mount_smb_share(self, host, share): + """ Mounts an SMB share and returns the result. """ + mountpoint = self.mountdir + '/'+args.mountprefix+'-smb_' + host + '_' + share.replace(' ', '_') + mkdir = ['mkdir', '-p', mountpoint] + subprocess.Popen(mkdir) + mount = ['mount', '-v', '-o', self.smbmntopt, '-t', self.smbtype, + '//' + self.smbuser + ':' + self.smbpass + '@' + host + '/' + share.replace(' ', '%20'), + mountpoint] + logger.debug('mount cmd: %s', mount) + process = subprocess.Popen(mount, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output = process.communicate() + if process.returncode > 0: + logger.debug('mount cmd exit code: %s', process.returncode) + mounted = False + try: + if os.path.exists(mountpoint): + os.rmdir(mountpoint) + except OSError: + raise OSError('error removing mountpoint directory') + else: + mounted = True + return {'host': host, 'sharetype': 'smb', 'sharename': share, 'mountpoint': mountpoint, + 'output': output, 'exitcode': process.returncode, 'mounted': mounted} def mount_shares(self): mount_status = [] - for hostdict in self.shares['nfsshares']: - hostname = hostdict['host'] - for share in hostdict['openshares']: - mountpoint = self.mountdir + '/'+args.mountprefix+'-nfs_' + hostname + '_' + share.replace('/', '_') - mkdir = ['mkdir', '-p', mountpoint] - subprocess.Popen(mkdir) - mount = ['mount', '-v', '-o', self.nfsmntopt, '-t', 'nfs', hostname + ':' + share, mountpoint] - logger.debug('mount cmd: %s', mount) - process = subprocess.Popen(mount, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - output = process.communicate() - if process.returncode > 0: - logger.debug('mount cmd exit code: %s', process.returncode) - mounted = False - try: - if os.path.exists(mountpoint): - os.rmdir(mountpoint) - except OSError: - raise OSError('error removing mountpoint directory') - else: - mounted = True - mount_status.append( - {'host': hostname, 'sharetype': 'nfs', 'sharename': share, 'mountpoint': mountpoint, - 'output': output, 'exitcode': process.returncode, 'mounted': mounted}) - for hostdict in self.shares['smbshares']: - hostname = hostdict['host'] - for share in hostdict['openshares']: - mountpoint = self.mountdir + '/'+args.mountprefix+'-smb_' + hostname + '_' + share.replace(' ', '_') - mkdir = ['mkdir', '-p', mountpoint] - subprocess.Popen(mkdir) - mount = ['mount', '-v', '-o', self.smbmntopt, '-t', self.smbtype, - '//' + self.smbuser + ':' + self.smbpass + '@' + hostname + '/' + share.replace(' ', '%20'), - mountpoint] - logger.debug('mount cmd: %s', mount) - process = subprocess.Popen(mount, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - output = process.communicate() - if process.returncode > 0: - logger.debug('mount cmd exit code: %s', process.returncode) - mounted = False - try: - if os.path.exists(mountpoint): - os.rmdir(mountpoint) - except OSError: - raise OSError('error removing mountpoint directory') - else: - mounted = True - mount_status.append( - {'host': hostname, 'sharetype': 'smb', 'sharename': share, 'mountpoint': mountpoint, - 'output': output, 'exitcode': process.returncode, 'mounted': mounted}) - return mount_status - - def umount_shares(self): - mount_status = [] - for hostdict in self.shares['nfsshares']: - hostname = hostdict['host'] - for share in hostdict['openshares']: - mountpoint = self.mountdir + '/'+args.mountprefix+'-nfs_' + hostname + '_' + share.replace('/', '_') - if os.path.ismount(mountpoint): - umount = ['umount', mountpoint] - subprocess.call(umount) - if os.path.exists(mountpoint): - os.rmdir(mountpoint) - for hostdict in self.shares['smbshares']: - hostname = hostdict['host'] - for share in hostdict['openshares']: - mountpoint = self.mountdir + '/'+args.mountprefix+'-smb_' + hostname + '_' + share.replace(' ', '_') - if os.path.ismount(mountpoint): - umount = ['umount', mountpoint] - subprocess.call(umount) - if os.path.exists(mountpoint): - os.rmdir(mountpoint) + + # Use ThreadPoolExecutor with max_workers set by user + with ThreadPoolExecutor(max_workers=self.max_workers) as executor: + future_to_mount = [] + + for hostdict in self.shares['nfsshares']: + for share in hostdict['openshares']: + future_to_mount.append(executor.submit(self.mount_nfs_share, hostdict['host'], share)) + + for hostdict in self.shares['smbshares']: + for share in hostdict['openshares']: + future_to_mount.append(executor.submit(self.mount_smb_share, hostdict['host'], share)) + + for future in as_completed(future_to_mount): + mount_status.append(future.result()) + return mount_status @@ -332,7 +323,8 @@ def sniff_network(): sniff = sniffer(hosts=args.hosts, excludehosts=args.excludehosts, nfs=args.nfs, smb=args.smb, smbuser=args.smbuser, - smbpass=args.smbpass) + smbpass=args.smbpass, max_workers=args.maxworkers) + shares = sniff.sniff_hosts() hostlist_nfs, hostlist_smb = sniff.sniff_hosts() shares = {'nfsshares': [], 'smbshares': []} if len(hostlist_nfs) > 0 or len(hostlist_smb) > 0: @@ -417,6 +409,8 @@ def auto_mounter(shares): # parse cli args parser = argparse.ArgumentParser() + parser.add_argument("--maxworkers", type=int, default=10, + help="Maximum number of concurrent threads for scanning and mounting (default: 10)") parser.add_argument("--hosts", metavar="HOSTS", help="Hosts to scan, example: 10.10.56.0/22 or 10.10.56.2 (default: scan all hosts)") parser.add_argument("-e", "--excludehosts", metavar="EXCLUDEHOSTS", @@ -527,10 +521,14 @@ def auto_mounter(shares): # get shares and mountpoints shares = sniff_network() if args.automount: - mountstocrawl = auto_mounter(shares) + mmounts = mounter(shares, mountdir=args.mountpoint, nfsmntopt=args.nfsmntopt, + smbmntopt=args.smbmntopt, smbtype=args.smbtype, + smbuser=args.smbuser, smbpass=args.smbpass, max_workers=args.maxworkers) + mountstocrawl = mounts.mount_shares() if args.quiet: for m in mountstocrawl: print(m) else: logger.info('Skipping auto-mount, exiting') + sys.exit(0) From 5fe9a34305e216ecd9ca2d9e8e856190c244310a Mon Sep 17 00:00:00 2001 From: Francisco Da Gainsburra <47154227+FashyGainz@users.noreply.github.com> Date: Tue, 10 Sep 2024 08:49:39 -0500 Subject: [PATCH 08/15] Fixed sniffer class moved the get_host_ranges method into the sniffer class, ensuring that it can be accessed using self.get_host_ranges() as expected --- sharesniffer.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/sharesniffer.py b/sharesniffer.py index 405cc20..e7205cd 100644 --- a/sharesniffer.py +++ b/sharesniffer.py @@ -47,7 +47,34 @@ def __init__(self, hosts=None, excludehosts=None, nfs=False, smb=False, smbuser= self.excludehosts = excludehosts self.nm = nmap.PortScanner() self.max_workers = max_workers - # Snipped the speedlevel setup for brevity (it remains the same) + + def get_host_ranges(self): + """ Retrieves the network ranges for scanning. """ + cidr = [] + for ifacename in netifaces.interfaces(): + try: + addrs = netifaces.ifaddresses(ifacename) + addr = addrs[netifaces.AF_INET] + ip = addr[0]['addr'] + except KeyError: + continue + if ip == '127.0.0.1' or ip == 'fe80::1%lo0': + continue + try: + netmask = addr[0]['netmask'].split('.') + except KeyError: + cidr.append(ip + '/' + '32') + continue + ipaddr = ip.split('.') + net_start = [str(int(ipaddr[x]) & int(netmask[x])) + for x in range(0, 4)] + binary_str = '' + for octet in netmask: + binary_str += bin(int(octet))[2:].zfill(8) + net_size = str(len(binary_str.rstrip('0'))) + cidr.append('.'.join(net_start) + '/' + net_size) + hostlist = ' '.join(cidr) + return hostlist def scan_host(self, host): """ Scans a single host and returns NFS/SMB open ports. """ @@ -64,6 +91,7 @@ def scan_host(self, host): return host, open_ports def sniff_hosts(self): + """ Sniffs for NFS/SMB shares on the network. """ hostlist_nfs = [] hostlist_smb = [] if not self.hosts: @@ -73,7 +101,6 @@ def sniff_hosts(self): else: hosts = self.hosts - # Use ThreadPoolExecutor with max_workers set by user with ThreadPoolExecutor(max_workers=self.max_workers) as executor: future_to_host = {executor.submit(self.scan_host, host): host for host in hosts.split()} for future in as_completed(future_to_host): From 459dada221ddd75e29de7427c75ebefb53364f7c Mon Sep 17 00:00:00 2001 From: Francisco Da Gainsburra <47154227+FashyGainz@users.noreply.github.com> Date: Tue, 10 Sep 2024 08:50:59 -0500 Subject: [PATCH 09/15] added concurrent.futures import --- sharesniffer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sharesniffer.py b/sharesniffer.py index e7205cd..98cb59e 100644 --- a/sharesniffer.py +++ b/sharesniffer.py @@ -31,6 +31,7 @@ import os import sys from random import randint +from concurrent.futures import ThreadPoolExecutor, as_completed SHARESNIFFER_VERSION = '0.1-b.8' From d09ffe489f464aba1fb5a53992c81687023bcc0a Mon Sep 17 00:00:00 2001 From: Francisco Da Gainsburra <47154227+FashyGainz@users.noreply.github.com> Date: Tue, 10 Sep 2024 08:57:27 -0500 Subject: [PATCH 10/15] added nmapargs --- sharesniffer.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sharesniffer.py b/sharesniffer.py index 98cb59e..4ebb7cf 100644 --- a/sharesniffer.py +++ b/sharesniffer.py @@ -39,7 +39,7 @@ class sniffer: - def __init__(self, hosts=None, excludehosts=None, nfs=False, smb=False, smbuser='guest', smbpass='', max_workers=10): + def __init__(self, hosts=None, excludehosts=None, nfs=False, smb=False, smbuser='guest', smbpass='', max_workers=10, speedlevel=4): self.hosts = hosts self.nfs = nfs self.smb = smb @@ -48,6 +48,8 @@ def __init__(self, hosts=None, excludehosts=None, nfs=False, smb=False, smbuser= self.excludehosts = excludehosts self.nm = nmap.PortScanner() self.max_workers = max_workers + self.speedlevel = speedlevel + self.nmapargs = f'-T{self.speedlevel}' # <-- Initialize nmapargs based on speed level def get_host_ranges(self): """ Retrieves the network ranges for scanning. """ @@ -80,7 +82,7 @@ def get_host_ranges(self): def scan_host(self, host): """ Scans a single host and returns NFS/SMB open ports. """ open_ports = {'nfs': False, 'smb': False} - self.nm.scan(host, '111,445', arguments=self.nmapargs) + self.nm.scan(host, '111,445', arguments=self.nmapargs) # Now nmapargs is defined for proto in self.nm[host].all_protocols(): lport = self.nm[host][proto].keys() for port in lport: From 5d4479131327e416b1ce160e052d0dbfbad43455 Mon Sep 17 00:00:00 2001 From: Francisco Da Gainsburra <47154227+FashyGainz@users.noreply.github.com> Date: Tue, 10 Sep 2024 09:02:18 -0500 Subject: [PATCH 11/15] Fixed using cidr notation expand_cidr Method: This method takes a CIDR notation (like 10.185.80.0/24) and expands it into individual IP addresses using the ipaddress module. It handles errors if an invalid CIDR is provided. Modified sniff_hosts: When CIDR notation is detected in the hosts string, it expands the range using the expand_cidr method. This generates a list of individual IP addresses that can be scanned. --- sharesniffer.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/sharesniffer.py b/sharesniffer.py index 4ebb7cf..2dbbce6 100644 --- a/sharesniffer.py +++ b/sharesniffer.py @@ -30,6 +30,7 @@ import subprocess import os import sys +import ipaddress from random import randint from concurrent.futures import ThreadPoolExecutor, as_completed @@ -49,7 +50,7 @@ def __init__(self, hosts=None, excludehosts=None, nfs=False, smb=False, smbuser= self.nm = nmap.PortScanner() self.max_workers = max_workers self.speedlevel = speedlevel - self.nmapargs = f'-T{self.speedlevel}' # <-- Initialize nmapargs based on speed level + self.nmapargs = f'-T{self.speedlevel}' # Initialize nmapargs based on speed level def get_host_ranges(self): """ Retrieves the network ranges for scanning. """ @@ -79,10 +80,19 @@ def get_host_ranges(self): hostlist = ' '.join(cidr) return hostlist + def expand_cidr(self, hosts): + """ Expands a CIDR range into individual IPs. """ + try: + ip_network = ipaddress.ip_network(hosts, strict=False) # Expand network range + return [str(ip) for ip in ip_network.hosts()] + except ValueError as e: + logger.error(f"Invalid CIDR notation: {hosts} - {e}") + return [] + def scan_host(self, host): """ Scans a single host and returns NFS/SMB open ports. """ open_ports = {'nfs': False, 'smb': False} - self.nm.scan(host, '111,445', arguments=self.nmapargs) # Now nmapargs is defined + self.nm.scan(host, '111,445', arguments=self.nmapargs) for proto in self.nm[host].all_protocols(): lport = self.nm[host][proto].keys() for port in lport: @@ -104,8 +114,16 @@ def sniff_hosts(self): else: hosts = self.hosts + # Expand CIDR ranges into individual IP addresses + expanded_hosts = [] + for host in hosts.split(): + if '/' in host: # If CIDR notation is present + expanded_hosts.extend(self.expand_cidr(host)) + else: + expanded_hosts.append(host) + with ThreadPoolExecutor(max_workers=self.max_workers) as executor: - future_to_host = {executor.submit(self.scan_host, host): host for host in hosts.split()} + future_to_host = {executor.submit(self.scan_host, host): host for host in expanded_hosts} for future in as_completed(future_to_host): host, open_ports = future.result() if open_ports['nfs']: From 0b833e2137b32afb79e5a823fca9374496bfc4d5 Mon Sep 17 00:00:00 2001 From: Francisco Da Gainsburra <47154227+FashyGainz@users.noreply.github.com> Date: Tue, 10 Sep 2024 09:05:20 -0500 Subject: [PATCH 12/15] Update sharesniffer.py --- sharesniffer.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/sharesniffer.py b/sharesniffer.py index 2dbbce6..b3d6c06 100644 --- a/sharesniffer.py +++ b/sharesniffer.py @@ -39,6 +39,8 @@ __version__ = SHARESNIFFER_VERSION +import ipaddress # Add this import to handle CIDR blocks + class sniffer: def __init__(self, hosts=None, excludehosts=None, nfs=False, smb=False, smbuser='guest', smbpass='', max_workers=10, speedlevel=4): self.hosts = hosts @@ -82,12 +84,17 @@ def get_host_ranges(self): def expand_cidr(self, hosts): """ Expands a CIDR range into individual IPs. """ - try: - ip_network = ipaddress.ip_network(hosts, strict=False) # Expand network range - return [str(ip) for ip in ip_network.hosts()] - except ValueError as e: - logger.error(f"Invalid CIDR notation: {hosts} - {e}") - return [] + expanded_ips = [] + for host in hosts.split(): + if '/' in host: # If CIDR notation is present + try: + ip_network = ipaddress.ip_network(host, strict=False) + expanded_ips.extend([str(ip) for ip in ip_network.hosts()]) + except ValueError as e: + logger.error(f"Invalid CIDR notation: {host} - {e}") + else: + expanded_ips.append(host) + return expanded_ips def scan_host(self, host): """ Scans a single host and returns NFS/SMB open ports. """ @@ -114,13 +121,8 @@ def sniff_hosts(self): else: hosts = self.hosts - # Expand CIDR ranges into individual IP addresses - expanded_hosts = [] - for host in hosts.split(): - if '/' in host: # If CIDR notation is present - expanded_hosts.extend(self.expand_cidr(host)) - else: - expanded_hosts.append(host) + # Expand any CIDR ranges into individual IP addresses + expanded_hosts = self.expand_cidr(hosts) with ThreadPoolExecutor(max_workers=self.max_workers) as executor: future_to_host = {executor.submit(self.scan_host, host): host for host in expanded_hosts} From ae9ddc96c5fb5272b553c3e7b36bc36811834fc9 Mon Sep 17 00:00:00 2001 From: Francisco Da Gainsburra <47154227+FashyGainz@users.noreply.github.com> Date: Tue, 10 Sep 2024 09:08:34 -0500 Subject: [PATCH 13/15] Update sharesniffer.py self.nm.all_hosts(): This method returns a list of all hosts that Nmap scanned and returned results for. If the target host (host) is not in this list, it means Nmap couldn't get any information for that host. Logging: A warning is logged when Nmap does not return results for a host, which could help in debugging why a particular host was skipped. Graceful Return: If the host is not in the scan results, the method returns the host and the empty open_ports dictionary, skipping further processing. --- sharesniffer.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/sharesniffer.py b/sharesniffer.py index b3d6c06..3fede1a 100644 --- a/sharesniffer.py +++ b/sharesniffer.py @@ -97,19 +97,29 @@ def expand_cidr(self, hosts): return expanded_ips def scan_host(self, host): - """ Scans a single host and returns NFS/SMB open ports. """ - open_ports = {'nfs': False, 'smb': False} - self.nm.scan(host, '111,445', arguments=self.nmapargs) - for proto in self.nm[host].all_protocols(): - lport = self.nm[host][proto].keys() - for port in lport: - if self.nm[host][proto][port]['state'] == 'open': - if port == 111: - open_ports['nfs'] = True - if port == 445: - open_ports['smb'] = True + """ Scans a single host and returns NFS/SMB open ports. """ + open_ports = {'nfs': False, 'smb': False} + logger.info(f"Scanning host {host}...") + + # Perform the Nmap scan + self.nm.scan(host, '111,445', arguments=self.nmapargs) + + # Check if the host is in the scan results + if host not in self.nm.all_hosts(): + logger.warning(f"No scan results for host: {host}") return host, open_ports + # Proceed only if the host exists in the scan results + for proto in self.nm[host].all_protocols(): + lport = self.nm[host][proto].keys() + for port in lport: + if self.nm[host][proto][port]['state'] == 'open': + if port == 111: + open_ports['nfs'] = True + if port == 445: + open_ports['smb'] = True + return host, open_ports + def sniff_hosts(self): """ Sniffs for NFS/SMB shares on the network. """ hostlist_nfs = [] From 02f98092a877f85a26e3bced76b863590bca63d8 Mon Sep 17 00:00:00 2001 From: Francisco Da Gainsburra <47154227+FashyGainz@users.noreply.github.com> Date: Tue, 10 Sep 2024 09:12:01 -0500 Subject: [PATCH 14/15] Update sharesniffer.py --- sharesniffer.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/sharesniffer.py b/sharesniffer.py index 3fede1a..ecea7f6 100644 --- a/sharesniffer.py +++ b/sharesniffer.py @@ -97,29 +97,29 @@ def expand_cidr(self, hosts): return expanded_ips def scan_host(self, host): - """ Scans a single host and returns NFS/SMB open ports. """ - open_ports = {'nfs': False, 'smb': False} - logger.info(f"Scanning host {host}...") - - # Perform the Nmap scan - self.nm.scan(host, '111,445', arguments=self.nmapargs) - - # Check if the host is in the scan results - if host not in self.nm.all_hosts(): - logger.warning(f"No scan results for host: {host}") + """ Scans a single host and returns NFS/SMB open ports. """ + open_ports = {'nfs': False, 'smb': False} + logger.info(f"Scanning host {host}...") + + # Perform the Nmap scan + self.nm.scan(host, '111,445', arguments=self.nmapargs) + + # Check if the host is in the scan results + if host not in self.nm.all_hosts(): + logger.warning(f"No scan results for host: {host}") + return host, open_ports + + # Proceed only if the host exists in the scan results + for proto in self.nm[host].all_protocols(): + lport = self.nm[host][proto].keys() + for port in lport: + if self.nm[host][proto][port]['state'] == 'open': + if port == 111: + open_ports['nfs'] = True + if port == 445: + open_ports['smb'] = True return host, open_ports - # Proceed only if the host exists in the scan results - for proto in self.nm[host].all_protocols(): - lport = self.nm[host][proto].keys() - for port in lport: - if self.nm[host][proto][port]['state'] == 'open': - if port == 111: - open_ports['nfs'] = True - if port == 445: - open_ports['smb'] = True - return host, open_ports - def sniff_hosts(self): """ Sniffs for NFS/SMB shares on the network. """ hostlist_nfs = [] From 0cef1cfeca941ae251f95899fc00af804ec0c012 Mon Sep 17 00:00:00 2001 From: Francisco Da Gainsburra <47154227+FashyGainz@users.noreply.github.com> Date: Tue, 10 Sep 2024 10:06:12 -0500 Subject: [PATCH 15/15] Update .todos --- .todos | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.todos b/.todos index ac9cddf..69307ef 100644 --- a/.todos +++ b/.todos @@ -1,6 +1,7 @@ -- Add concurrency +- Add concurrency *COMPLETED 091024* - sniffer - specify threads - concurrent futures -- Add extra error handling and logging + +- Add extra error handling and logging *COMPLETED 091024*