Skip to content

Commit 09af023

Browse files
committed
Merge in parser
1 parent 15313a9 commit 09af023

File tree

2 files changed

+309
-0
lines changed

2 files changed

+309
-0
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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+
pass = 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? or 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 => pass,
60+
:CHANGED => changed
61+
}
62+
63+
result.merge!({ :EXPIRES => expires }) unless expires.nil? or expires.empty?
64+
result.merge!({ :NEVER_EXPIRE => never_expires }) unless never_expires.nil? or never_expires.empty?
65+
result.merge!({ :DISABLED => disabled }) unless disabled.nil? or disabled.empty?
66+
result.merge!({ :PATH => path }) unless path.nil? or path.empty?
67+
result.merge!({ :DATASOURCE => dsn }) unless dsn.nil? or dsn.empty?
68+
result.merge!({ :DRIVER => driver }) unless driver.nil? or driver.empty?
69+
result.merge!({ :TASK => app_name }) unless app_name.nil? or app_name.empty?
70+
result.merge!({ :SERVICE => service }) unless service.nil? or 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+
return 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::Ui::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? or dc.empty?
106+
table << ["DOMAIN", domain] unless domain.nil? or domain.empty?
107+
table << ["CHANGED", result[:CHANGED]]
108+
table << ["EXPIRES", result[:EXPIRES]] unless result[:EXPIRES].nil? or result[:EXPIRES].empty?
109+
table << ["NEVER_EXPIRES?", result[:NEVER_EXPIRE]] unless result[:NEVER_EXPIRE].nil? or result[:NEVER_EXPIRE].empty?
110+
table << ["DISABLED", result[:DISABLED]] unless result[:DISABLED].nil? or result[:DISABLED].empty?
111+
table << ["PATH", result[:PATH]] unless result[:PATH].nil? or result[:PATH].empty?
112+
table << ["DATASOURCE", result[:DSN]] unless result[:DSN].nil? or result[:DSN].empty?
113+
table << ["DRIVER", result[:DRIVER]] unless result[:DRIVER].nil? or result[:DRIVER].empty?
114+
table << ["TASK", result[:TASK]] unless result[:TASK].nil? or result[:TASK].empty?
115+
table << ["SERVICE", result[:SERVICE]] unless result[:SERVICE].nil? or result[:SERVICE].empty?
116+
117+
unless result[:ATTRIBUTES].nil? or 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+
return 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+
unless encrypted_data
133+
return ""
134+
end
135+
136+
pass = ""
137+
padding = "=" * (4 - (encrypted_data.length % 4))
138+
epassword = "#{encrypted_data}#{padding}"
139+
decoded = Rex::Text.decode_base64(epassword)
140+
141+
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"
142+
aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
143+
begin
144+
aes.decrypt
145+
aes.key = key
146+
plaintext = aes.update(decoded)
147+
plaintext << aes.final
148+
pass = plaintext.unpack('v*').pack('C*') # UNICODE conversion
149+
rescue OpenSSL::Cipher::CipherError => e
150+
puts "Unable to decode: \"#{encrypted_data}\" Exception: #{e}"
151+
end
152+
153+
return pass
154+
end
155+
156+
end
157+
end
158+
end
159+
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
require 'rex/parser/group_policy_preferences'
2+
3+
xml_group = '
4+
<?xml version="1.0" encoding="utf-8"?>
5+
<Groups clsid="{3125E937-EB16-4b4c-9934-544FC6D24D26}"><User clsid="{DF5F1855-51E5-4d24-8B1A-D9BDE98BA1D1}" name="SuperSecretBackdoor" image="0" changed="2013-04-25 18:36:07" uid="{B5EDB865-34F5-4BD7-9C59-3AEB1C7A68C3}"><Properties action="C" fullName="" description="" cpassword="VBQUNbDhuVti3/GHTGHPvcno2vH3y8e8m1qALVO1H3T0rdkr2rub1smfTtqRBRI3" changeLogon="0" noChange="0" neverExpires="1" acctDisabled="0" userName="SuperSecretBackdoor"/></User>
6+
</Groups>
7+
'
8+
9+
xml_datasrc = '
10+
<?xml version="1.0" encoding="utf-8"?>
11+
<DataSources clsid="{380F820F-F21B-41ac-A3CC-24D4F80F067B}"><DataSource clsid="{5C209626-D820-4d69-8D50-1FACD6214488}" userContext="1" name="test" image="0" changed="2013-04-25 20:39:08" uid="{3513F923-9661-4819-9995-91A63C7D7A65}"><Properties action="C" userDSN="0" dsn="test" driver="test" description="" username="test" cpassword="eYbbv1GZI4DZEgTXPUDspw"><Attributes><Attribute name="test" value="test"/><Attribute name="test2" value="test2"/></Attributes></Properties></DataSource>
12+
</DataSources>
13+
'
14+
15+
xml_drive = '
16+
<?xml version="1.0" encoding="utf-8"?>
17+
<Drives clsid="{8FDDCC1A-0C3C-43cd-A6B4-71A6DF20DA8C}"><Drive clsid="{935D1B74-9CB8-4e3c-9914-7DD559B7A417}" name="E:" status="E:" image="0" changed="2013-04-25 20:33:02" uid="{016E2095-EAB5-43C0-8BCF-4C2655F709F5}"><Properties action="C" thisDrive="NOCHANGE" allDrives="NOCHANGE" userName="drivemap" path="drivemap" label="" persistent="0" useLetter="1" letter="E" cpassword="Lj3fkZ8E3AFAJPTSoBitKw"/></Drive>
18+
</Drives>
19+
'
20+
21+
xml_schd = '
22+
<?xml version="1.0" encoding="utf-8"?>
23+
<ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}"><Task clsid="{2DEECB1C-261F-4e13-9B21-16FB83BC03BD}" name="test1" image="2" changed="2013-04-25 20:30:13" uid="{41059D76-C7B4-4D05-9679-AE7510247B1F}"><Properties action="U" name="test1" appName="notepad.exe" args="" startIn="" comment="" runAs="test1" cpassword="DdGgLn/bpUNU/QjjcNvn4A" enabled="0"><Triggers><Trigger type="DAILY" startHour="8" startMinutes="0" beginYear="2013" beginMonth="4" beginDay="25" hasEndDate="0" repeatTask="0" interval="1"/></Triggers></Properties></Task>
24+
</ScheduledTasks>
25+
'
26+
27+
xml_serv = '
28+
<?xml version="1.0" encoding="utf-8"?>
29+
<NTServices clsid="{2CFB484A-4E96-4b5d-A0B6-093D2F91E6AE}"><NTService clsid="{AB6F0B67-341F-4e51-92F9-005FBFBA1A43}" name="Blah" image="0" changed="2013-04-25 20:29:49" uid="{C6AE4201-9F99-46AB-93C2-9D734D87D343}"><Properties startupType="NOCHANGE" serviceName="Blah" timeout="30" accountName="bob" cpassword="OQWR9sf5FTlGgh8SJX31ug"/></NTService>
30+
</NTServices>
31+
'
32+
33+
xml_ms = '
34+
<?xml version="1.0" encoding="utf-8"?>
35+
<Groups clsid="{3125E937-EB16-4b4c-9934-544FC6D24D26}"
36+
disabled="1">
37+
<User clsid="{DF5F1855-51E5-4d24-8B1A-D9BDE98BA1D1}"
38+
name="DbAdmin"
39+
image="2"
40+
changed="2007-07-06 20:45:20"
41+
uid="{253F4D90-150A-4EFB-BCC8-6E894A9105F7}">
42+
<Properties
43+
action="U"
44+
newName=""
45+
fullName="Database Admin"
46+
description="Local Database Admin"
47+
cpassword="demo"
48+
changeLogon="0"
49+
noChange="0"
50+
neverExpires="0"
51+
acctDisabled="1"
52+
userName="DbAdmin"/>
53+
</User>
54+
<Group clsid="{6D4A79E4-529C-4481-ABD0-F5BD7EA93BA7}"
55+
name="Database Admins"
56+
image="2"
57+
changed="2007-07-06 20:46:21"
58+
uid="{C5FB3901-508A-4A9E-9171-60D4FC2B404B}">
59+
<Properties
60+
action="U"
61+
newName=""
62+
description="Local Database Admins"
63+
userAction="REMOVE"
64+
deleteAllUsers="1"
65+
deleteAllGroups="1"
66+
removeAccounts="0"
67+
groupName="Database Admins">
68+
<Members>
69+
<Member
70+
name="domain\sampleuser"
71+
action="ADD"
72+
sid=""/>
73+
</Members>
74+
</Properties>
75+
</Group>
76+
</Groups>
77+
'
78+
79+
cpassword_normal = "j1Uyj3Vx8TY9LtLZil2uAuZkFQA/4latT76ZwgdHdhw"
80+
cpassword_bad = "blah"
81+
82+
describe Rex::Parser::GPP do
83+
GPP = Rex::Parser::GPP
84+
85+
##
86+
# Decrypt
87+
##
88+
it "Decrypt returns Local*P4ssword! for normal cpassword" do
89+
result = GPP.decrypt(cpassword_normal)
90+
result.should eq("Local*P4ssword!")
91+
end
92+
93+
it "Decrypt returns blank for bad cpassword" do
94+
result = GPP.decrypt(cpassword_bad)
95+
result.should eq("")
96+
end
97+
98+
it "Decrypt returns blank for nil cpassword" do
99+
result = GPP.decrypt(nil)
100+
result.should eq("")
101+
end
102+
103+
##
104+
# Parse
105+
##
106+
107+
it "Parse returns empty [] for nil" do
108+
GPP.parse(nil).should be_empty
109+
end
110+
111+
it "Parse returns results for xml_ms and password is empty" do
112+
results = GPP.parse(xml_ms)
113+
results.should_not be_empty
114+
results[0][:PASS].should be_empty
115+
end
116+
117+
it "Parse returns results for xml_datasrc, and attributes, and password is test1" do
118+
results = GPP.parse(xml_datasrc)
119+
results.should_not be_empty
120+
results[0].include?(:ATTRIBUTES).should be_true
121+
results[0][:ATTRIBUTES].should_not be_empty
122+
results[0][:PASS].should eq("test")
123+
end
124+
125+
xmls = []
126+
xmls << xml_group
127+
xmls << xml_drive
128+
xmls << xml_schd
129+
xmls << xml_serv
130+
xmls << xml_datasrc
131+
132+
it "Parse returns results for all good xmls and passwords" do
133+
xmls.each do |xml|
134+
results = GPP.parse(xml)
135+
results.should_not be_empty
136+
results[0][:PASS].should_not be_empty
137+
end
138+
end
139+
140+
##
141+
# Create_Tables
142+
##
143+
it "Create_tables returns tables for all good xmls" do
144+
xmls.each do |xml|
145+
results = GPP.parse(xml)
146+
tables = GPP.create_tables(results, "test")
147+
tables.should_not be_empty
148+
end
149+
end
150+
end

0 commit comments

Comments
 (0)