Skip to content

Commit 7044dab

Browse files
committed
Land rapid7#3600 - GPP Junk Padding Fix
2 parents f274eb7 + b4111df commit 7044dab

File tree

2 files changed

+114
-73
lines changed

2 files changed

+114
-73
lines changed

lib/rex/parser/group_policy_preferences.rb

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,40 @@ def self.create_tables(results, filetype, domain=nil, dc=nil)
129129
# Decrypts passwords using Microsoft's published key:
130130
# http://msdn.microsoft.com/en-us/library/cc422924.aspx
131131
def self.decrypt(encrypted_data)
132-
unless encrypted_data
133-
return ""
134-
end
132+
password = ""
133+
return password unless encrypted_data
135134

136135
password = ""
137-
padding = "=" * (4 - (encrypted_data.length % 4))
138-
epassword = "#{encrypted_data}#{padding}"
139-
decoded = Rex::Text.decode_base64(epassword)
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
140166

141167
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"
142168
aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC")

spec/lib/rex/parser/group_policy_preferences_spec.rb

Lines changed: 82 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# encoding: binary
12
require 'rex/parser/group_policy_preferences'
23

34
xml_group = '
@@ -76,75 +77,89 @@
7677
</Groups>
7778
'
7879

80+
# Win2k8 appears to append some junk padding in some cases
81+
cpassword_win2k8 = []
82+
# Win2k8R2 - EqWFlA4kn2T6PHvGi09M7seHuqCYK/slkJWIl7mK+wEMON8tIIslS6707RU1F7Bh
83+
cpassword_win2k8 << ['EqWFlA4kn2T6PHvGi09M7seHuqCYK/slkJWIl7mK+wEMON8tIIslS6707RU1F7BhTµkp', 'N3v3rGunnaG!veYo']
84+
cpassword_win2k8 << ['EqWFlA4kn2T6PHvGi09M7seHuqCYK/slkJWIl7mK+wGSwOI7Be//GJdxd5YYXUQHTµkp', 'N3v3rGunnaG!veYou']
85+
# Win2k8R2 - EqWFlA4kn2T6PHvGi09M7seHuqCYK/slkJWIl7mK+wFSuDccBEp/4l5EuKnwF0WS
86+
cpassword_win2k8 << ['EqWFlA4kn2T6PHvGi09M7seHuqCYK/slkJWIl7mK+wFSuDccBEp/4l5EuKnwF0WS»YÂVAA', 'N3v3rGunnaG!veYouUp']
7987
cpassword_normal = "j1Uyj3Vx8TY9LtLZil2uAuZkFQA/4latT76ZwgdHdhw"
8088
cpassword_bad = "blah"
8189

8290
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
91+
GPP = Rex::Parser::GPP
92+
93+
##
94+
# Decrypt
95+
##
96+
it "Decrypt returns Local*P4ssword! for normal cpassword" do
97+
result = GPP.decrypt(cpassword_normal)
98+
result.should eq("Local*P4ssword!")
99+
end
100+
101+
it "Decrypt returns blank for bad cpassword" do
102+
result = GPP.decrypt(cpassword_bad)
103+
result.should eq("")
104+
end
105+
106+
it "Decrypt returns blank for nil cpassword" do
107+
result = GPP.decrypt(nil)
108+
result.should eq("")
109+
end
110+
111+
it 'Decrypts a cpassword containing junk padding' do
112+
cpassword_win2k8.each do |encrypted, expected|
113+
result = GPP.decrypt(encrypted)
114+
result.should eq(expected)
115+
end
116+
end
117+
118+
##
119+
# Parse
120+
##
121+
122+
it "Parse returns empty [] for nil" do
123+
GPP.parse(nil).should be_empty
124+
end
125+
126+
it "Parse returns results for xml_ms and password is empty" do
127+
results = GPP.parse(xml_ms)
128+
results.should_not be_empty
129+
results[0][:PASS].should be_empty
130+
end
131+
132+
it "Parse returns results for xml_datasrc, and attributes, and password is test1" do
133+
results = GPP.parse(xml_datasrc)
134+
results.should_not be_empty
135+
results[0].include?(:ATTRIBUTES).should be_true
136+
results[0][:ATTRIBUTES].should_not be_empty
137+
results[0][:PASS].should eq("test")
138+
end
139+
140+
xmls = []
141+
xmls << xml_group
142+
xmls << xml_drive
143+
xmls << xml_schd
144+
xmls << xml_serv
145+
xmls << xml_datasrc
146+
147+
it "Parse returns results for all good xmls and passwords" do
148+
xmls.each do |xml|
149+
results = GPP.parse(xml)
150+
results.should_not be_empty
151+
results[0][:PASS].should_not be_empty
152+
end
153+
end
154+
155+
##
156+
# Create_Tables
157+
##
158+
it "Create_tables returns tables for all good xmls" do
159+
xmls.each do |xml|
160+
results = GPP.parse(xml)
161+
tables = GPP.create_tables(results, "test")
162+
tables.should_not be_empty
163+
end
164+
end
150165
end

0 commit comments

Comments
 (0)