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

class MetasploitModule < Msf::Auxiliary
  include Msf::Auxiliary::Report
  include Msf::Exploit::Remote::Udp
  include Msf::Auxiliary::UDPScanner
  include Msf::Auxiliary::NTP
  include Msf::Auxiliary::DRDoS

  def initialize
    super(
      'Name' => 'NTP Monitor List Scanner',
      'Description' => %q{
        This module identifies NTP servers which permit "monlist" queries and
        obtains the recent clients list. The monlist feature allows remote
        attackers to cause a denial of service (traffic amplification)
        via spoofed requests. The more clients there are in the list, the
        greater the amplification.
      },
      'References' => [
        ['CVE', '2013-5211'],
        ['URL', 'https://www.cisa.gov/uscert/ncas/alerts/TA14-013A'],
        ['URL', 'https://support.ntp.org/bin/view/Main/SecurityNotice'],
        ['URL', 'https://nmap.org/nsedoc/scripts/ntp-monlist.html'],
      ],
      'Author' => 'hdm',
      'License' => MSF_LICENSE
    )

    register_options(
      [
        OptInt.new('RETRY', [false, "Number of tries to query the NTP server", 3]),
        OptBool.new('SHOW_LIST', [false, 'Show the recent clients list', false])
      ]
    )

    register_advanced_options(
      [
        OptBool.new('StoreNTPClients', [true, 'Store NTP clients as host records in the database', false])
      ]
    )
  end

  # Called for each response packet
  def scanner_process(data, shost, sport)
    @results[shost] ||= { messages: [], peers: [] }
    @results[shost][:messages] << Rex::Proto::NTP::NTPPrivate.new.read(data).to_binary_s
    @results[shost][:peers] << extract_peer_tuples(data)
  end

  # Called before the scan block
  def scanner_prescan(batch)
    @results = {}
    @aliases = {}
    @probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 42, "\0" * 40).to_binary_s
  end

  # Called after the scan block
  def scanner_postscan(batch)
    @results.keys.each do |k|
      response_map = { @probe => @results[k][:messages] }
      peer = "#{k}:#{rport}"

      # TODO: check to see if any of the responses are actually NTP before reporting
      report_service(
        :host => k,
        :proto => 'udp',
        :port => rport,
        :name => 'ntp'
      )

      peers = @results[k][:peers].flatten(1)
      unless peers.empty?
        print_good("#{peer} NTP monlist request permitted (#{peers.length} entries)")
        # store the peers found from the monlist
        report_note(
          :host => k,
          :proto => 'udp',
          :port => rport,
          :type => 'ntp.monlist',
          :data => { :monlist => peers }
        )
        # print out peers if desired
        if datastore['SHOW_LIST']
          peers.each do |ntp_peer|
            print_status("#{peer} #{ntp_peer}")
          end
        end
        # store any aliases for our target
        report_note(
          :host => k,
          :proto => 'udp',
          :port => rport,
          :type => 'ntp.addresses',
          :data => { :addresses => peers.map { |p| p.last }.sort.uniq }
        )

        if (datastore['StoreNTPClients'])
          print_status("#{peer} Storing #{peers.length} NTP client hosts in the database...")
          peers.each do |r|
            maddr, mport, mserv = r
            next if maddr == '127.0.0.1' # some NTP servers peer with themselves..., but we can't store loopback

            report_note(
              :host => maddr,
              :type => 'ntp.client.history',
              :data => {
                :address => maddr,
                :port => mport,
                :server => mserv
              }
            )
          end
        end
      end

      vulnerable, proof = prove_amplification(response_map)
      what = 'NTP Mode 7 monlist DRDoS (CVE-2013-5211)'
      if vulnerable
        print_good("#{peer} - Vulnerable to #{what}: #{proof}")
        report_vuln({
          :host => k,
          :port => rport,
          :proto => 'udp',
          :name => what,
          :refs => self.references
        })
      else
        vprint_status("#{peer} - Not vulnerable to #{what}: #{proof}")
      end
    end
  end

  # Examine the monlist response +data+ and extract all peer tuples (saddd, dport, daddr)
  def extract_peer_tuples(data)
    return [] if data.length < 76

    # NTP headers 8 bytes
    ntp_flags, ntp_auth, ntp_vers, ntp_code = data.slice!(0, 4).unpack('C*')
    pcnt, plen = data.slice!(0, 4).unpack('nn')
    return [] if plen != 72

    idx = 0
    peer_tuples = []
    1.upto(pcnt) do
      # u_int32 firsttime; /* first time we received a packet */
      # u_int32 lasttime;  /* last packet from this host */
      # u_int32 restr;     /* restrict bits (was named lastdrop) */
      # u_int32 count;     /* count of packets received */
      # u_int32 addr;      /* host address V4 style */
      # u_int32 daddr;     /* destination host address */
      # u_int32 flags;     /* flags about destination */
      # u_short port;      /* port number of last reception */

      _, _, _, _, saddr, daddr, _, dport = data[idx, 30].unpack("NNNNNNNn")

      peer_tuples << [ Rex::Socket.addr_itoa(saddr), dport, Rex::Socket.addr_itoa(daddr) ]
      idx += plen
    end
    peer_tuples
  end
end
