-
Notifications
You must be signed in to change notification settings - Fork 14.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Land #19461, Modernize NetWkstaUserEnum
Modernize NetWkstaUserEnum in smb scanner
- Loading branch information
Showing
4 changed files
with
161 additions
and
158 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54959,7 +54959,8 @@ | |
"type": "auxiliary", | ||
"author": [ | ||
"natron <[email protected]>", | ||
"Joshua D. Abraham <[email protected]>" | ||
"Joshua D. Abraham <[email protected]>", | ||
"NtAlexio2 <[email protected]>" | ||
], | ||
"description": "Determine what domain users are logged into a remote system via a DCERPC to NetWkstaUserEnum.", | ||
"references": [ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
### | ||
# | ||
# The Workstation Service Remote Protocol is used to perform tasks on a computer remotely on a | ||
# network, including: | ||
# | ||
# - Configuring properties and behavior of a Server Message Block network | ||
# redirector (SMB network redirector). | ||
# - Managing domain membership and computer names. | ||
# - Gathering information, such as the number of enabled transport protocols and the number of | ||
# currently logged-on users. | ||
# | ||
# -*- coding: binary -*- | ||
|
||
module Msf | ||
|
||
module Exploit::Remote::MsWkst | ||
|
||
include Msf::Exploit::Remote::SMB::Client::Ipc | ||
|
||
class MsWkstError < StandardError; end | ||
class MsWkstConnectionError < MsWkstError; end | ||
class MsWkstAuthenticationError < MsWkstError; end | ||
class MsWkstUnexpectedReplyError < MsWkstError; end | ||
|
||
WKS_UUID = '6bffd098-a112-3610-9833-46c3f87e345a'.freeze | ||
WKS_VERS = '1.0'.freeze | ||
WKSSVC_ENDPOINT = RubySMB::Dcerpc::Wkssvc.freeze | ||
|
||
# The currently connected WKSSVC pipe | ||
attr_reader :wkssvc_pipe | ||
|
||
def user_enum(level) | ||
self.wkssvc_pipe.netr_wksta_user_enum( | ||
level: level | ||
) | ||
end | ||
|
||
def get_info() | ||
self.wkssvc_pipe.netr_wksta_get_info | ||
end | ||
|
||
def disconnect_wkssvc | ||
begin | ||
self.wkssvc_pipe.close if self.wkssvc_pipe&.is_connected? | ||
rescue RubySMB::Error::UnexpectedStatusCode, RubySMB::Error::CommunicationError => e | ||
wlog e | ||
end | ||
end | ||
|
||
module_function | ||
|
||
def connect_wkssvc(tree) | ||
begin | ||
vprint_status('Connecting to Workstation Service Remote Protocol') | ||
self.wkssvc_pipe = tree.open_file(filename: 'wkssvc', write: true, read: true) | ||
|
||
raise MsWkstConnectionError.new('Could not open wkssvc pipe on remote SMB server.') unless wkssvc_pipe | ||
|
||
vprint_status('Binding to \\wkssvc...') | ||
self.wkssvc_pipe.bind(endpoint: WKSSVC_ENDPOINT) | ||
vprint_good('Bound to \\wkssvc') | ||
|
||
self.wkssvc_pipe | ||
rescue RubySMB::Dcerpc::Error::FaultError => e | ||
elog(e.message, error: e) | ||
raise MsWkstUnexpectedReplyError, "Connection failed (DCERPC fault: #{e.status_name})" | ||
end | ||
end | ||
|
||
protected | ||
|
||
attr_writer :wkssvc_pipe | ||
|
||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
class MetasploitModule < Msf::Auxiliary | ||
|
||
# Exploit mixins should be called first | ||
include Msf::Exploit::Remote::MsWkst | ||
include Msf::Exploit::Remote::SMB::Client | ||
include Msf::Exploit::Remote::SMB::Client::Authenticated | ||
include Msf::Exploit::Remote::DCERPC | ||
|
@@ -24,191 +25,116 @@ def initialize | |
[ | ||
'natron', # original module | ||
'Joshua D. Abraham <jabra[at]praetorian.com>', # database storage | ||
'NtAlexio2 <[email protected]>', # refactor | ||
], | ||
'References' => | ||
[ | ||
[ 'URL', 'https://docs.microsoft.com/en-us/windows/win32/api/lmwksta/nf-lmwksta-netwkstauserenum' ] | ||
], | ||
'License' => MSF_LICENSE, | ||
) | ||
|
||
deregister_options('RPORT') | ||
|
||
end | ||
|
||
def parse_value(resp, idx) | ||
#val_length = resp[idx,4].unpack("V")[0] | ||
idx += 4 | ||
#val_offset = resp[idx,4].unpack("V")[0] | ||
idx += 4 | ||
val_actual = resp[idx,4].unpack("V")[0] | ||
idx += 4 | ||
value = resp[idx,val_actual*2] | ||
idx += val_actual * 2 | ||
|
||
idx += val_actual % 2 * 2 # alignment | ||
|
||
return value,idx | ||
def rport | ||
@rport | ||
end | ||
|
||
def parse_net_wksta_enum_users_info(resp) | ||
accounts = [ Hash.new() ] | ||
|
||
idx = 20 | ||
count = resp[idx,4].unpack("V")[0] # wkssvc_NetWkstaEnumUsersInfo -> Info -> PtrCt0 -> User() -> Ptr -> Max Count | ||
idx += 4 | ||
|
||
1.upto(count) do | ||
# wkssvc_NetWkstaEnumUsersInfo -> Info -> PtrCt0 -> User() -> Ptr -> Ref ID | ||
idx += 4 # ref id name | ||
idx += 4 # ref id logon domain | ||
idx += 4 # ref id other domains | ||
idx += 4 # ref id logon server | ||
end | ||
|
||
1.upto(count) do | ||
# wkssvc_NetWkstaEnumUsersInfo -> Info -> PtrCt0 -> User() -> Ptr -> ID1 max count | ||
|
||
account_name,idx = parse_value(resp, idx) | ||
logon_domain,idx = parse_value(resp, idx) | ||
other_domains,idx = parse_value(resp, idx) | ||
logon_server,idx = parse_value(resp, idx) | ||
|
||
accounts << { | ||
:account_name => account_name, | ||
:logon_domain => logon_domain, | ||
:other_domains => other_domains, | ||
:logon_server => logon_server | ||
} | ||
end | ||
|
||
accounts | ||
def smb_direct | ||
@smb_direct | ||
end | ||
|
||
def rport | ||
@rport || datastore['RPORT'] | ||
def connect(*args, **kwargs) | ||
super(*args, **kwargs, direct: @smb_direct) | ||
end | ||
|
||
def smb_direct | ||
@smbdirect || datastore['SMBDirect'] | ||
def run_session | ||
smb_services = [{ port: self.simple.peerport, direct: self.simple.direct }] | ||
smb_services.map { |smb_service| run_service(smb_service[:port], smb_service[:direct]) } | ||
end | ||
|
||
def store_username(username, res, ip, rport) | ||
service_data = { | ||
address: ip, | ||
port: rport, | ||
service_name: 'smb', | ||
protocol: 'tcp', | ||
workspace_id: myworkspace_id, | ||
proof: res | ||
} | ||
|
||
credential_data = { | ||
origin_type: :service, | ||
module_fullname: fullname, | ||
username: username | ||
} | ||
|
||
credential_data.merge!(service_data) | ||
|
||
credential_core = create_credential(credential_data) | ||
|
||
login_data = { | ||
core: credential_core, | ||
status: Metasploit::Model::Login::Status::UNTRIED | ||
} | ||
|
||
login_data.merge!(service_data) | ||
create_credential_login(login_data) | ||
end | ||
def run_rhost | ||
if datastore['RPORT'].blank? || datastore['RPORT'] == 0 | ||
smb_services = [ | ||
{ port: 445, direct: true }, | ||
{ port: 139, direct: false } | ||
] | ||
else | ||
smb_services = [ | ||
{ port: datastore['RPORT'], direct: datastore['SMBDirect'] } | ||
] | ||
end | ||
|
||
def run_host(ip) | ||
smb_services.map { |smb_service| run_service(smb_service[:port], smb_service[:direct]) } | ||
end | ||
|
||
ports = [139, 445] | ||
def run_service(port, direct) | ||
@rport = port | ||
@smb_direct = direct | ||
|
||
ipc_tree = connect_ipc | ||
wkssvc_pipe = connect_wkssvc(ipc_tree) | ||
endpoint = RubySMB::Dcerpc::Wkssvc.freeze | ||
|
||
user_info = user_enum(endpoint::WKSTA_USER_INFO_1) | ||
user_info.wkui1_buffer | ||
|
||
rescue Msf::Exploit::Remote::SMB::Client::Ipc::SmbIpcAuthenticationError => e | ||
print_warning(e.message) | ||
nil | ||
rescue RubySMB::Error::RubySMBError => e | ||
print_error("Error: #{e.message}") | ||
nil | ||
rescue ::Timeout::Error | ||
rescue ::Exception => e | ||
print_error("Error: #{e.class} #{e}") | ||
nil | ||
ensure | ||
disconnect_wkssvc | ||
end | ||
|
||
def run_host(_ip) | ||
if session | ||
print_status("Using existing session #{session.sid}") | ||
client = session.client | ||
self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client) | ||
ports = [simple.port] | ||
self.simple.connect("\\\\#{simple.address}\\IPC$") # smb_login connects to this share for some reason and it doesn't work unless we do too | ||
self.simple = session.simple_client | ||
results = run_session | ||
else | ||
results = run_rhost | ||
end | ||
|
||
ports.each do |port| | ||
unless results.to_s.empty? | ||
accounts = [ Hash.new() ] | ||
results.compact.each do |result_set| | ||
result_set.each { |result| accounts << { | ||
:account_name => result.wkui1_username.encode('UTF-8'), | ||
:logon_domain => result.wkui1_logon_domain.encode('UTF-8'), | ||
:other_domains => result.wkui1_oth_domains.encode('UTF-8'), | ||
:logon_server => result.wkui1_logon_server.encode('UTF-8')} } | ||
end | ||
accounts.shift | ||
|
||
@rport = port | ||
begin | ||
unless session | ||
connect() | ||
smb_login() | ||
if datastore['VERBOSE'] | ||
accounts.each do |x| | ||
print_status x[:logon_domain] + "\\" + x[:account_name] + | ||
"\t(logon_server: #{x[:logon_server]}, other_domains: #{x[:other_domains]})" | ||
end | ||
else | ||
print_status "#{accounts.collect{|x| x[:logon_domain] + "\\" + x[:account_name]}.join(", ")}" | ||
end | ||
|
||
found_accounts = [] | ||
accounts.each do |x| | ||
comp_user = x[:logon_domain] + "\\" + x[:account_name] | ||
found_accounts.push(comp_user.scan(/[[:print:]]/).join) unless found_accounts.include?(comp_user.scan(/[[:print:]]/).join) | ||
end | ||
|
||
uuid = [ '6bffd098-a112-3610-9833-46c3f87e345a', '1.0' ] | ||
|
||
handle = dcerpc_handle_target( | ||
uuid[0], uuid[1], 'ncacn_np', ["\\wkssvc"], simple.address | ||
) | ||
begin | ||
dcerpc_bind(handle) | ||
stub = | ||
NDR.uwstring("\\\\" + simple.address) + # Server Name | ||
NDR.long(1) + # Level | ||
NDR.long(1) + # Ctr | ||
NDR.long(rand(0xffffffff)) + # ref id | ||
NDR.long(0) + # entries read | ||
NDR.long(0) + # null ptr to user0 | ||
|
||
NDR.long(0xffffffff) + # Prefmaxlen | ||
NDR.long(rand(0xffffffff)) + # ref id | ||
NDR.long(0) # null ptr to resume handle | ||
|
||
dcerpc.call(2,stub) | ||
|
||
resp = dcerpc.last_response ? dcerpc.last_response.stub_data : nil | ||
|
||
accounts = parse_net_wksta_enum_users_info(resp) | ||
accounts.shift | ||
|
||
if datastore['VERBOSE'] | ||
accounts.each do |x| | ||
print_status x[:logon_domain] + "\\" + x[:account_name] + | ||
"\t(logon_server: #{x[:logon_server]}, other_domains: #{x[:other_domains]})" | ||
end | ||
else | ||
print_status "#{accounts.collect{|x| x[:logon_domain] + "\\" + x[:account_name]}.join(", ")}" | ||
end | ||
|
||
found_accounts = [] | ||
accounts.each do |x| | ||
comp_user = x[:logon_domain] + "\\" + x[:account_name] | ||
found_accounts.push(comp_user.scan(/[[:print:]]/).join) unless found_accounts.include?(comp_user.scan(/[[:print:]]/).join) | ||
end | ||
|
||
found_accounts.each do |comp_user| | ||
if comp_user.to_s =~ /\$$/ | ||
next | ||
end | ||
|
||
print_good("Found user: #{comp_user}") | ||
store_username(comp_user, resp, simple.address, rport) | ||
end | ||
|
||
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e | ||
print_error("UUID #{uuid[0]} #{uuid[1]} ERROR 0x%.8x" % e.error_code) | ||
#puts e | ||
#return | ||
rescue ::Exception => e | ||
print_error("UUID #{uuid[0]} #{uuid[1]} ERROR #{$!}") | ||
#puts e | ||
#return | ||
found_accounts.each do |comp_user| | ||
if comp_user.to_s =~ /\$$/ | ||
next | ||
end | ||
|
||
disconnect() | ||
return | ||
rescue ::Exception | ||
print_line($!.to_s) | ||
print_good("Found user: #{comp_user}") | ||
end | ||
|
||
end | ||
end | ||
|
||
end | ||
end |