forked from jeffhacks/smbscan
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscan.py
143 lines (125 loc) · 5.06 KB
/
scan.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import csv
import ipaddress
import logging
import socket
import traceback
import random
import time
from slugify import slugify
import scan_internals
logger = logging.getLogger('smbscan')
class Target:
def __init__(self, target):
self.ip = None
self.name = None
self.alias = ""
self.addressList = ""
self.shares = []
try:
# Assume target is an IP
self.ip = str(ipaddress.ip_address(target))
hostname, self.alias, self.addressList = socket.gethostbyaddr(str(self.ip))
if is_valid_hostname(hostname):
self.name = hostname
else:
self.name = self.ip
except socket.herror:
# No DNS resolution
self.name = self.ip
except ValueError:
# Target is not an IP
try:
self.ip = socket.gethostbyname(target)
self.name = target
except socket.gaierror as e:
logger.error(f"Target failure ({target}): {str(e)}")
except Exception as e:
logger.error(f"Target failure ({target}): {str(e)}")
class User:
def __init__(self, username = "Guest", password = "", domain = "", lmhash = "", nthash = ""):
self.username = username
self.password = password
self.domain = domain
self.lmhash = lmhash
self.nthash = nthash
self.results = []
def scan_single(targetHost, user, options):
if str(targetHost) in options.excludeHosts:
logger.warning(
"Skipping %1s (on exclusion list)" % (targetHost)
)
if is_host_in_statefile(options.stateFile, str(targetHost)):
logger.warning(
"Skipping %1s (already scanned)" % (targetHost)
)
else:
logger.info(f'Scanning {targetHost}')
target = Target(str(targetHost))
smbClient = None
targetScanResult = ''
if not target.ip:
targetScanResult = 'Unable to resolve'
else:
smbClient = scan_internals.get_client(target, user, options, 445)
# TODO This could potentially be noisier than needed. Consider only using port 445
# if (smbClient is None):
# smbClient = get_client(target, user, options, 139)
if smbClient is None:
targetScanResult = 'Unable to connect'
else:
fileTimeStamp = time.strftime("%Y%m%d-%H%M%S")
logfileName = (
options.logDirectory
+ "/smbscan-"
+ slugify(target.name)
+ "-"
+ fileTimeStamp
+ ".csv"
)
if scan_internals.is_safe_filepath(options.logDirectory, logfileName):
try:
logfile = open(logfileName, "a")
logger.info(f"{target.ip} ({target.name}) Connected as {user.username}, Target OS: {smbClient.getServerOS()}")
target.shares = scan_internals.get_shares(smbClient)
if options.crawlShares:
scan_internals.get_files(smbClient, target, options, logfile)
user.results.append(target)
except Exception as e:
targetScanResult = 'Error'
logger.exception(f'General failure ({targetHost}): {str(e)}')
#print(traceback.format_exc())
finally:
targetScanResult = 'Scan completed'
smbClient.close()
logfile.close()
add_target_to_statefile(options.stateFile, str(targetHost), targetScanResult)
if options.jitterTarget > 0:
time.sleep(random.randint(0, options.jitterTarget))
def is_valid_hostname(hostname):
""""Returns True if host name does not contain illegal characters, as described in Microsoft Docs."""
# https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/naming-conventions-for-computer-domain-site-ou
illegalCharacters = ['\\','/',':','*','?','"','<','>','|']
if any(char in hostname for char in illegalCharacters):
logger.warning(f'Invalid hostname: {hostname}, contains illegal characters')
return False
else:
return True
def add_target_to_statefile(statefileName, targetHost, targetScanResult):
row = [
targetHost,
time.strftime("%Y%m%d %H%M%S"),
targetScanResult
]
with open(statefileName, 'a+', encoding='utf-8') as statefile:
writer = csv.writer(statefile)
writer.writerow(row)
def is_host_in_statefile(statefileName, targetHost):
found = False
try:
with open(statefileName, 'r', encoding='utf-8') as statefile:
reader = csv.reader(statefile, delimiter=',')
found = any(targetHost == row[0].strip() for row in reader)
except Exception as e:
pass
finally:
return found