Skip to content

Commit 409b1ae

Browse files
committed
Land rapid7#19461, Modernize NetWkstaUserEnum
Modernize NetWkstaUserEnum in smb scanner
2 parents 1a14916 + 7abfb6c commit 409b1ae

File tree

4 files changed

+161
-158
lines changed

4 files changed

+161
-158
lines changed

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ GEM
495495
ruby-progressbar (1.13.0)
496496
ruby-rc4 (0.1.5)
497497
ruby2_keywords (0.0.5)
498-
ruby_smb (3.3.9)
498+
ruby_smb (3.3.10)
499499
bindata (= 2.4.15)
500500
openssl-ccm
501501
openssl-cmac

db/modules_metadata_base.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54959,7 +54959,8 @@
5495954959
"type": "auxiliary",
5496054960
"author": [
5496154961
"natron <[email protected]>",
54962-
"Joshua D. Abraham <[email protected]>"
54962+
"Joshua D. Abraham <[email protected]>",
54963+
"NtAlexio2 <[email protected]>"
5496354964
],
5496454965
"description": "Determine what domain users are logged into a remote system via a DCERPC to NetWkstaUserEnum.",
5496554966
"references": [
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
###
2+
#
3+
# The Workstation Service Remote Protocol is used to perform tasks on a computer remotely on a
4+
# network, including:
5+
#
6+
# - Configuring properties and behavior of a Server Message Block network
7+
# redirector (SMB network redirector).
8+
# - Managing domain membership and computer names.
9+
# - Gathering information, such as the number of enabled transport protocols and the number of
10+
# currently logged-on users.
11+
#
12+
# -*- coding: binary -*-
13+
14+
module Msf
15+
16+
module Exploit::Remote::MsWkst
17+
18+
include Msf::Exploit::Remote::SMB::Client::Ipc
19+
20+
class MsWkstError < StandardError; end
21+
class MsWkstConnectionError < MsWkstError; end
22+
class MsWkstAuthenticationError < MsWkstError; end
23+
class MsWkstUnexpectedReplyError < MsWkstError; end
24+
25+
WKS_UUID = '6bffd098-a112-3610-9833-46c3f87e345a'.freeze
26+
WKS_VERS = '1.0'.freeze
27+
WKSSVC_ENDPOINT = RubySMB::Dcerpc::Wkssvc.freeze
28+
29+
# The currently connected WKSSVC pipe
30+
attr_reader :wkssvc_pipe
31+
32+
def user_enum(level)
33+
self.wkssvc_pipe.netr_wksta_user_enum(
34+
level: level
35+
)
36+
end
37+
38+
def get_info()
39+
self.wkssvc_pipe.netr_wksta_get_info
40+
end
41+
42+
def disconnect_wkssvc
43+
begin
44+
self.wkssvc_pipe.close if self.wkssvc_pipe&.is_connected?
45+
rescue RubySMB::Error::UnexpectedStatusCode, RubySMB::Error::CommunicationError => e
46+
wlog e
47+
end
48+
end
49+
50+
module_function
51+
52+
def connect_wkssvc(tree)
53+
begin
54+
vprint_status('Connecting to Workstation Service Remote Protocol')
55+
self.wkssvc_pipe = tree.open_file(filename: 'wkssvc', write: true, read: true)
56+
57+
raise MsWkstConnectionError.new('Could not open wkssvc pipe on remote SMB server.') unless wkssvc_pipe
58+
59+
vprint_status('Binding to \\wkssvc...')
60+
self.wkssvc_pipe.bind(endpoint: WKSSVC_ENDPOINT)
61+
vprint_good('Bound to \\wkssvc')
62+
63+
self.wkssvc_pipe
64+
rescue RubySMB::Dcerpc::Error::FaultError => e
65+
elog(e.message, error: e)
66+
raise MsWkstUnexpectedReplyError, "Connection failed (DCERPC fault: #{e.status_name})"
67+
end
68+
end
69+
70+
protected
71+
72+
attr_writer :wkssvc_pipe
73+
74+
end
75+
76+
end

modules/auxiliary/scanner/smb/smb_enumusers_domain.rb

Lines changed: 82 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
class MetasploitModule < Msf::Auxiliary
77

88
# Exploit mixins should be called first
9+
include Msf::Exploit::Remote::MsWkst
910
include Msf::Exploit::Remote::SMB::Client
1011
include Msf::Exploit::Remote::SMB::Client::Authenticated
1112
include Msf::Exploit::Remote::DCERPC
@@ -24,191 +25,116 @@ def initialize
2425
[
2526
'natron', # original module
2627
'Joshua D. Abraham <jabra[at]praetorian.com>', # database storage
28+
'NtAlexio2 <[email protected]>', # refactor
2729
],
2830
'References' =>
2931
[
3032
[ 'URL', 'https://docs.microsoft.com/en-us/windows/win32/api/lmwksta/nf-lmwksta-netwkstauserenum' ]
3133
],
3234
'License' => MSF_LICENSE,
3335
)
34-
35-
deregister_options('RPORT')
36-
3736
end
3837

39-
def parse_value(resp, idx)
40-
#val_length = resp[idx,4].unpack("V")[0]
41-
idx += 4
42-
#val_offset = resp[idx,4].unpack("V")[0]
43-
idx += 4
44-
val_actual = resp[idx,4].unpack("V")[0]
45-
idx += 4
46-
value = resp[idx,val_actual*2]
47-
idx += val_actual * 2
48-
49-
idx += val_actual % 2 * 2 # alignment
50-
51-
return value,idx
38+
def rport
39+
@rport
5240
end
5341

54-
def parse_net_wksta_enum_users_info(resp)
55-
accounts = [ Hash.new() ]
56-
57-
idx = 20
58-
count = resp[idx,4].unpack("V")[0] # wkssvc_NetWkstaEnumUsersInfo -> Info -> PtrCt0 -> User() -> Ptr -> Max Count
59-
idx += 4
60-
61-
1.upto(count) do
62-
# wkssvc_NetWkstaEnumUsersInfo -> Info -> PtrCt0 -> User() -> Ptr -> Ref ID
63-
idx += 4 # ref id name
64-
idx += 4 # ref id logon domain
65-
idx += 4 # ref id other domains
66-
idx += 4 # ref id logon server
67-
end
68-
69-
1.upto(count) do
70-
# wkssvc_NetWkstaEnumUsersInfo -> Info -> PtrCt0 -> User() -> Ptr -> ID1 max count
71-
72-
account_name,idx = parse_value(resp, idx)
73-
logon_domain,idx = parse_value(resp, idx)
74-
other_domains,idx = parse_value(resp, idx)
75-
logon_server,idx = parse_value(resp, idx)
76-
77-
accounts << {
78-
:account_name => account_name,
79-
:logon_domain => logon_domain,
80-
:other_domains => other_domains,
81-
:logon_server => logon_server
82-
}
83-
end
84-
85-
accounts
42+
def smb_direct
43+
@smb_direct
8644
end
8745

88-
def rport
89-
@rport || datastore['RPORT']
46+
def connect(*args, **kwargs)
47+
super(*args, **kwargs, direct: @smb_direct)
9048
end
9149

92-
def smb_direct
93-
@smbdirect || datastore['SMBDirect']
50+
def run_session
51+
smb_services = [{ port: self.simple.peerport, direct: self.simple.direct }]
52+
smb_services.map { |smb_service| run_service(smb_service[:port], smb_service[:direct]) }
9453
end
9554

96-
def store_username(username, res, ip, rport)
97-
service_data = {
98-
address: ip,
99-
port: rport,
100-
service_name: 'smb',
101-
protocol: 'tcp',
102-
workspace_id: myworkspace_id,
103-
proof: res
104-
}
105-
106-
credential_data = {
107-
origin_type: :service,
108-
module_fullname: fullname,
109-
username: username
110-
}
111-
112-
credential_data.merge!(service_data)
113-
114-
credential_core = create_credential(credential_data)
115-
116-
login_data = {
117-
core: credential_core,
118-
status: Metasploit::Model::Login::Status::UNTRIED
119-
}
120-
121-
login_data.merge!(service_data)
122-
create_credential_login(login_data)
123-
end
55+
def run_rhost
56+
if datastore['RPORT'].blank? || datastore['RPORT'] == 0
57+
smb_services = [
58+
{ port: 445, direct: true },
59+
{ port: 139, direct: false }
60+
]
61+
else
62+
smb_services = [
63+
{ port: datastore['RPORT'], direct: datastore['SMBDirect'] }
64+
]
65+
end
12466

125-
def run_host(ip)
67+
smb_services.map { |smb_service| run_service(smb_service[:port], smb_service[:direct]) }
68+
end
12669

127-
ports = [139, 445]
70+
def run_service(port, direct)
71+
@rport = port
72+
@smb_direct = direct
73+
74+
ipc_tree = connect_ipc
75+
wkssvc_pipe = connect_wkssvc(ipc_tree)
76+
endpoint = RubySMB::Dcerpc::Wkssvc.freeze
77+
78+
user_info = user_enum(endpoint::WKSTA_USER_INFO_1)
79+
user_info.wkui1_buffer
80+
81+
rescue Msf::Exploit::Remote::SMB::Client::Ipc::SmbIpcAuthenticationError => e
82+
print_warning(e.message)
83+
nil
84+
rescue RubySMB::Error::RubySMBError => e
85+
print_error("Error: #{e.message}")
86+
nil
87+
rescue ::Timeout::Error
88+
rescue ::Exception => e
89+
print_error("Error: #{e.class} #{e}")
90+
nil
91+
ensure
92+
disconnect_wkssvc
93+
end
12894

95+
def run_host(_ip)
12996
if session
130-
print_status("Using existing session #{session.sid}")
131-
client = session.client
132-
self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client)
133-
ports = [simple.port]
134-
self.simple.connect("\\\\#{simple.address}\\IPC$") # smb_login connects to this share for some reason and it doesn't work unless we do too
97+
self.simple = session.simple_client
98+
results = run_session
99+
else
100+
results = run_rhost
135101
end
136102

137-
ports.each do |port|
103+
unless results.to_s.empty?
104+
accounts = [ Hash.new() ]
105+
results.compact.each do |result_set|
106+
result_set.each { |result| accounts << {
107+
:account_name => result.wkui1_username.encode('UTF-8'),
108+
:logon_domain => result.wkui1_logon_domain.encode('UTF-8'),
109+
:other_domains => result.wkui1_oth_domains.encode('UTF-8'),
110+
:logon_server => result.wkui1_logon_server.encode('UTF-8')} }
111+
end
112+
accounts.shift
138113

139-
@rport = port
140-
begin
141-
unless session
142-
connect()
143-
smb_login()
114+
if datastore['VERBOSE']
115+
accounts.each do |x|
116+
print_status x[:logon_domain] + "\\" + x[:account_name] +
117+
"\t(logon_server: #{x[:logon_server]}, other_domains: #{x[:other_domains]})"
144118
end
119+
else
120+
print_status "#{accounts.collect{|x| x[:logon_domain] + "\\" + x[:account_name]}.join(", ")}"
121+
end
122+
123+
found_accounts = []
124+
accounts.each do |x|
125+
comp_user = x[:logon_domain] + "\\" + x[:account_name]
126+
found_accounts.push(comp_user.scan(/[[:print:]]/).join) unless found_accounts.include?(comp_user.scan(/[[:print:]]/).join)
127+
end
145128

146-
uuid = [ '6bffd098-a112-3610-9833-46c3f87e345a', '1.0' ]
147-
148-
handle = dcerpc_handle_target(
149-
uuid[0], uuid[1], 'ncacn_np', ["\\wkssvc"], simple.address
150-
)
151-
begin
152-
dcerpc_bind(handle)
153-
stub =
154-
NDR.uwstring("\\\\" + simple.address) + # Server Name
155-
NDR.long(1) + # Level
156-
NDR.long(1) + # Ctr
157-
NDR.long(rand(0xffffffff)) + # ref id
158-
NDR.long(0) + # entries read
159-
NDR.long(0) + # null ptr to user0
160-
161-
NDR.long(0xffffffff) + # Prefmaxlen
162-
NDR.long(rand(0xffffffff)) + # ref id
163-
NDR.long(0) # null ptr to resume handle
164-
165-
dcerpc.call(2,stub)
166-
167-
resp = dcerpc.last_response ? dcerpc.last_response.stub_data : nil
168-
169-
accounts = parse_net_wksta_enum_users_info(resp)
170-
accounts.shift
171-
172-
if datastore['VERBOSE']
173-
accounts.each do |x|
174-
print_status x[:logon_domain] + "\\" + x[:account_name] +
175-
"\t(logon_server: #{x[:logon_server]}, other_domains: #{x[:other_domains]})"
176-
end
177-
else
178-
print_status "#{accounts.collect{|x| x[:logon_domain] + "\\" + x[:account_name]}.join(", ")}"
179-
end
180-
181-
found_accounts = []
182-
accounts.each do |x|
183-
comp_user = x[:logon_domain] + "\\" + x[:account_name]
184-
found_accounts.push(comp_user.scan(/[[:print:]]/).join) unless found_accounts.include?(comp_user.scan(/[[:print:]]/).join)
185-
end
186-
187-
found_accounts.each do |comp_user|
188-
if comp_user.to_s =~ /\$$/
189-
next
190-
end
191-
192-
print_good("Found user: #{comp_user}")
193-
store_username(comp_user, resp, simple.address, rport)
194-
end
195-
196-
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
197-
print_error("UUID #{uuid[0]} #{uuid[1]} ERROR 0x%.8x" % e.error_code)
198-
#puts e
199-
#return
200-
rescue ::Exception => e
201-
print_error("UUID #{uuid[0]} #{uuid[1]} ERROR #{$!}")
202-
#puts e
203-
#return
129+
found_accounts.each do |comp_user|
130+
if comp_user.to_s =~ /\$$/
131+
next
204132
end
205133

206-
disconnect()
207-
return
208-
rescue ::Exception
209-
print_line($!.to_s)
134+
print_good("Found user: #{comp_user}")
210135
end
136+
211137
end
212-
end
213138

139+
end
214140
end

0 commit comments

Comments
 (0)