172 lines
6.0 KiB
Ruby
172 lines
6.0 KiB
Ruby
# -*- coding: binary -*-
|
|
|
|
module Rex
|
|
module Parser
|
|
|
|
# This is a parser for the Windows Unattended Answer File
|
|
# format. It's used by modules/post/windows/gather/enum_unattend.rb
|
|
# and uses REXML (as opposed to Nokogiri) for its XML parsing.
|
|
# See: http://technet.microsoft.com/en-us/library/ff715801
|
|
# http://technet.microsoft.com/en-us/library/cc749415(v=ws.10).aspx
|
|
# Samples: http://technet.microsoft.com/en-us/library/cc732280%28v=ws.10%29.aspx
|
|
class Unattend
|
|
|
|
require 'rex/text'
|
|
|
|
def self.parse(xml)
|
|
return [] if xml.nil?
|
|
results = []
|
|
unattend = xml.elements['unattend']
|
|
return [] if unattend.nil?
|
|
unattend.each_element do |settings|
|
|
next if settings.class != REXML::Element
|
|
settings.get_elements('component').each do |c|
|
|
next if c.class != REXML::Element
|
|
results << extract_useraccounts(c.elements['UserAccounts'])
|
|
results << extract_autologon(c.elements['AutoLogon'])
|
|
results << extract_deployment(c.elements['WindowsDeploymentServices'])
|
|
results << extract_domain_join(c.elements['Identification/Credentials'])
|
|
end
|
|
end
|
|
return results.flatten
|
|
end
|
|
|
|
#
|
|
# Extract sensitive data from Deployment Services.
|
|
# We can only seem to add one <Login> with Windows System Image Manager, so
|
|
# we'll only enum one.
|
|
#
|
|
def self.extract_deployment(deployment)
|
|
return [] if deployment.nil?
|
|
domain = deployment.elements['Login/Credentials/Domain'].get_text.value rescue ''
|
|
username = deployment.elements['Login/Credentials/Username'].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'
|
|
|
|
if plaintext == 'false'
|
|
password = Rex::Text.decode_base64(password)
|
|
password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '')
|
|
end
|
|
|
|
return {'type' => 'wds', 'domain' => domain, 'username' => username, 'password' => password }
|
|
end
|
|
|
|
#
|
|
# Extract sensitive data from 'Secure' Domain Join
|
|
#
|
|
def self.extract_domain_join(credentials)
|
|
return [] if credentials.nil?
|
|
domain = credentials.elements['Domain'].get_text.value rescue ''
|
|
username = credentials.elements['Username'].get_text.value rescue ''
|
|
password = credentials.elements['Password'].get_text.value rescue ''
|
|
|
|
return {'type' => 'domain_join', 'domain' => domain, 'username' => username, 'password' => password }
|
|
end
|
|
|
|
#
|
|
# Extract sensitive data from AutoLogon
|
|
#
|
|
def self.extract_autologon(auto_logon)
|
|
return [] if auto_logon.nil?
|
|
|
|
domain = auto_logon.elements['Domain'].get_text.value rescue ''
|
|
username = auto_logon.elements['Username'].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'
|
|
|
|
if plaintext == 'false'
|
|
password = Rex::Text.decode_base64(password)
|
|
password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '')
|
|
end
|
|
|
|
return {'type' => 'auto', 'domain' => domain, 'username' => username, 'password' => password }
|
|
end
|
|
|
|
#
|
|
# Extract sensitive data from UserAccounts
|
|
#
|
|
def self.extract_useraccounts(user_accounts)
|
|
return[] if user_accounts.nil?
|
|
|
|
results = []
|
|
account_types = ['AdministratorPassword', 'DomainAccounts', 'LocalAccounts']
|
|
account_types.each do |t|
|
|
element = user_accounts.elements[t]
|
|
next if element.nil?
|
|
|
|
case t
|
|
#
|
|
# Extract the password from AdministratorPasswords
|
|
#
|
|
when account_types[0]
|
|
password = element.elements['Value'].get_text.value rescue ''
|
|
plaintext = element.elements['PlainText'].get_text.value rescue 'true'
|
|
|
|
if plaintext == 'false'
|
|
password = Rex::Text.decode_base64(password)
|
|
password = password.gsub(/#{Rex::Text.to_unicode('AdministratorPassword')}$/, '')
|
|
end
|
|
|
|
unless password.empty?
|
|
results << {'type' => 'admin', 'username' => 'Administrator', 'password' => password}
|
|
end
|
|
|
|
#
|
|
# Extract the sensitive data from DomainAccounts.
|
|
# According to MSDN, unattend.xml doesn't seem to store passwords for domain accounts
|
|
#
|
|
when account_types[1] #DomainAccounts
|
|
element.elements.each do |account_list|
|
|
name = account_list.elements['DomainAccount/Name'].get_text.value rescue ''
|
|
group = account_list.elements['DomainAccount/Group'].get_text.value rescue 'true'
|
|
|
|
results << {'type' => 'domain', 'username' => name, 'group' => group}
|
|
end
|
|
#
|
|
# Extract the username/password from LocalAccounts
|
|
#
|
|
when account_types[2] #LocalAccounts
|
|
element.elements.each do |local|
|
|
password = local.elements['Password/Value'].get_text.value rescue ''
|
|
plaintext = local.elements['Password/PlainText'].get_text.value rescue 'true'
|
|
|
|
if plaintext == 'false'
|
|
password = Rex::Text.decode_base64(password)
|
|
password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '')
|
|
end
|
|
|
|
username = local.elements['Name'].get_text.value rescue ''
|
|
results << {'type' => 'local', 'username' => username, 'password' => password}
|
|
end
|
|
end
|
|
end
|
|
|
|
return results
|
|
end
|
|
|
|
def self.create_table(results)
|
|
return nil if results.nil? or results.empty?
|
|
table = Rex::Text::Table.new({
|
|
'Header' => 'Unattend Credentials',
|
|
'Indent' => 1,
|
|
'Columns' => ['Type', 'Domain', 'Username', 'Password', 'Groups']
|
|
})
|
|
|
|
results.each do |result|
|
|
case result['type']
|
|
when 'wds', 'auto', 'domain_join'
|
|
table << [result['type'], result['domain'], result['username'], result['password'], ""]
|
|
when 'admin', 'local'
|
|
table << [result['type'], "", result['username'], result['password'], ""]
|
|
when 'domain'
|
|
table << [result['type'], "", result['username'], "", result['group']]
|
|
end
|
|
end
|
|
|
|
return table
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|