CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/parser/group_policy_preferences.rb
Views: 1904
1
# -*- coding: binary -*-
2
#
3
4
module Rex
5
module Parser
6
7
# This is a parser for the Windows Group Policy Preferences file
8
# format. It's used by modules/post/windows/gather/credentials/gpp.rb
9
# and uses REXML (as opposed to Nokogiri) for its XML parsing.
10
# See: http://msdn.microsoft.com/en-gb/library/cc232587.aspx
11
class GPP
12
require 'rex'
13
require 'rexml/document'
14
15
def self.parse(data)
16
if data.nil?
17
return []
18
end
19
20
xml = REXML::Document.new(data).root
21
results = []
22
23
unless xml and xml.elements and xml.elements.to_a("//Properties")
24
return []
25
end
26
27
xml.elements.to_a("//Properties").each do |node|
28
epassword = node.attributes['cpassword']
29
next if epassword.to_s.empty?
30
password = self.decrypt(epassword)
31
32
user = node.attributes['runAs'] if node.attributes['runAs']
33
user = node.attributes['accountName'] if node.attributes['accountName']
34
user = node.attributes['username'] if node.attributes['username']
35
user = node.attributes['userName'] if node.attributes['userName']
36
user = node.attributes['newName'] unless node.attributes['newName'].nil? || node.attributes['newName'].empty?
37
changed = node.parent.attributes['changed']
38
39
# Printers and Shares
40
path = node.attributes['path']
41
42
# Datasources
43
dsn = node.attributes['dsn']
44
driver = node.attributes['driver']
45
46
# Tasks
47
app_name = node.attributes['appName']
48
49
# Services
50
service = node.attributes['serviceName']
51
52
# Groups
53
expires = node.attributes['expires']
54
never_expires = node.attributes['neverExpires']
55
disabled = node.attributes['acctDisabled']
56
57
result = {
58
:USER => user,
59
:PASS => password,
60
:CHANGED => changed
61
}
62
63
result.merge!({ :EXPIRES => expires }) unless expires.nil? || expires.empty?
64
result.merge!({ :NEVER_EXPIRES => never_expires.to_i }) unless never_expires.nil? || never_expires.empty?
65
result.merge!({ :DISABLED => disabled.to_i }) unless disabled.nil? || disabled.empty?
66
result.merge!({ :PATH => path }) unless path.nil? || path.empty?
67
result.merge!({ :DATASOURCE => dsn }) unless dsn.nil? || dsn.empty?
68
result.merge!({ :DRIVER => driver }) unless driver.nil? || driver.empty?
69
result.merge!({ :TASK => app_name }) unless app_name.nil? || app_name.empty?
70
result.merge!({ :SERVICE => service }) unless service.nil? || service.empty?
71
72
attributes = []
73
node.elements.each('//Attributes//Attribute') do |dsn_attribute|
74
attributes << {
75
:A_NAME => dsn_attribute.attributes['name'],
76
:A_VALUE => dsn_attribute.attributes['value']
77
}
78
end
79
80
result.merge!({ :ATTRIBUTES => attributes }) unless attributes.empty?
81
82
results << result
83
end
84
85
results
86
end
87
88
def self.create_tables(results, filetype, domain=nil, dc=nil)
89
tables = []
90
results.each do |result|
91
table = Rex::Text::Table.new(
92
'Header' => 'Group Policy Credential Info',
93
'Indent' => 1,
94
'SortIndex' => -1,
95
'Columns' =>
96
[
97
'Name',
98
'Value',
99
]
100
)
101
102
table << ["TYPE", filetype]
103
table << ["USERNAME", result[:USER]]
104
table << ["PASSWORD", result[:PASS]]
105
table << ["DOMAIN CONTROLLER", dc] unless dc.nil? || dc.empty?
106
table << ["DOMAIN", domain] unless domain.nil? || domain.empty?
107
table << ["CHANGED", result[:CHANGED]]
108
table << ["EXPIRES", result[:EXPIRES]] unless result[:EXPIRES].nil? || result[:EXPIRES].empty?
109
table << ["NEVER_EXPIRES?", result[:NEVER_EXPIRES]] unless result[:NEVER_EXPIRES].nil?
110
table << ["DISABLED", result[:DISABLED]] unless result[:DISABLED].nil?
111
table << ["PATH", result[:PATH]] unless result[:PATH].nil? || result[:PATH].empty?
112
table << ["DATASOURCE", result[:DSN]] unless result[:DSN].nil? || result[:DSN].empty?
113
table << ["DRIVER", result[:DRIVER]] unless result[:DRIVER].nil? || result[:DRIVER].empty?
114
table << ["TASK", result[:TASK]] unless result[:TASK].nil? || result[:TASK].empty?
115
table << ["SERVICE", result[:SERVICE]] unless result[:SERVICE].nil? || result[:SERVICE].empty?
116
117
unless result[:ATTRIBUTES].nil? || result[:ATTRIBUTES].empty?
118
result[:ATTRIBUTES].each do |dsn_attribute|
119
table << ["ATTRIBUTE", "#{dsn_attribute[:A_NAME]} - #{dsn_attribute[:A_VALUE]}"]
120
end
121
end
122
123
tables << table
124
end
125
126
tables
127
end
128
129
# Decrypts passwords using Microsoft's published key:
130
# http://msdn.microsoft.com/en-us/library/cc422924.aspx
131
def self.decrypt(encrypted_data)
132
password = ""
133
return password unless encrypted_data
134
135
password = ""
136
retries = 0
137
original_data = encrypted_data.dup
138
139
begin
140
mod = encrypted_data.length % 4
141
142
# PowerSploit code strips the last character, unsure why...
143
case mod
144
when 1
145
encrypted_data = encrypted_data[0..-2]
146
when 2, 3
147
padding = '=' * (4 - mod)
148
encrypted_data = "#{encrypted_data}#{padding}"
149
end
150
151
# Strict base64 decoding used here
152
decoded = encrypted_data.unpack('m0').first
153
rescue ::ArgumentError => e
154
# Appears to be some junk UTF-8 Padding appended at times in
155
# Win2k8 (not in Win2k8R2)
156
# Lets try stripping junk and see if we can decrypt
157
if retries < 8
158
retries += 1
159
original_data = original_data[0..-2]
160
encrypted_data = original_data
161
retry
162
else
163
return password
164
end
165
end
166
167
key = "\x4e\x99\x06\xe8\xfc\xb6\x6c\xc9\xfa\xf4\x93\x10\x62\x0f\xfe\xe8\xf4\x96\xe8\x06\xcc\x05\x79\x90\x20\x9b\x09\xa4\x33\xb6\x6c\x1b"
168
aes = OpenSSL::Cipher.new("AES-256-CBC")
169
begin
170
aes.decrypt
171
aes.key = key
172
plaintext = aes.update(decoded)
173
plaintext << aes.final
174
password = plaintext.unpack('v*').pack('C*') # UNICODE conversion
175
rescue OpenSSL::Cipher::CipherError => e
176
puts "Unable to decode: \"#{encrypted_data}\" Exception: #{e}"
177
end
178
179
password
180
end
181
182
end
183
end
184
end
185
186
187