165 lines
5.3 KiB
Ruby
165 lines
5.3 KiB
Ruby
##
|
|
# 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.us-cert.gov/ncas/alerts/TA14-013A'],
|
|
['URL', 'http://support.ntp.org/bin/view/Main/SecurityNotice'],
|
|
['URL', 'http://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 reponse +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
|