Skip to content

Commit 7ad9300

Browse files
committed
Update ntp_monlist to use UDPScanner, NTP and DRDoS mixins
1 parent 8fd4ee8 commit 7ad9300

File tree

1 file changed

+101
-89
lines changed

1 file changed

+101
-89
lines changed

modules/auxiliary/scanner/ntp/ntp_monlist.rb

Lines changed: 101 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
class Metasploit3 < Msf::Auxiliary
99

1010
include Msf::Auxiliary::Report
11-
include Msf::Auxiliary::Scanner
11+
include Msf::Exploit::Remote::Udp
12+
include Msf::Auxiliary::UDPScanner
13+
include Msf::Auxiliary::NTP
14+
include Msf::Auxiliary::DRDoS
1215

1316
def initialize
1417
super(
@@ -33,10 +36,7 @@ def initialize
3336

3437
register_options(
3538
[
36-
Opt::RPORT(123),
37-
Opt::CHOST,
3839
OptInt.new('RETRY', [false, "Number of tries to query the NTP server", 3]),
39-
OptInt.new('BATCHSIZE', [true, 'The number of hosts to probe in each set', 256]),
4040
OptBool.new('SHOW_LIST', [false, 'Show the recent clients list', 'false'])
4141
], self.class)
4242

@@ -46,112 +46,129 @@ def initialize
4646
], self.class)
4747
end
4848

49-
# Define our batch size
50-
def run_batch_size
51-
datastore['BATCHSIZE'].to_i
49+
# Called for each IP in the batch
50+
def scan_host(ip)
51+
scanner_send(@probe, ip, datastore['RPORT'])
5252
end
5353

54-
# Fingerprint a single host
55-
def run_batch(batch)
54+
# Called for each response packet
55+
def scanner_process(data, shost, sport)
56+
@results[shost] ||= { messages: [], peers: [] }
57+
@results[shost][:messages] << Rex::Proto::NTP::NTPPrivate.new(data)
58+
@results[shost][:peers] << extract_peer_tuples(data)
59+
end
5660

61+
# Called before the scan block
62+
def scanner_prescan(batch)
5763
@results = {}
5864
@aliases = {}
65+
@probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 42)
66+
end
5967

60-
vprint_status("Sending probes to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)")
61-
62-
begin
63-
udp_sock = nil
64-
idx = 0
65-
66-
# Create an unbound UDP socket if no CHOST is specified, otherwise
67-
# create a UDP socket bound to CHOST (in order to avail of pivoting)
68-
udp_sock = Rex::Socket::Udp.create({
69-
'LocalHost' => datastore['CHOST'] || nil,
70-
'Context' => {'Msf' => framework, 'MsfExploit' => self}
71-
})
72-
add_socket(udp_sock)
73-
74-
# Try more times since NTP servers can be a bit busy
75-
1.upto(datastore['RETRY'].to_i) do
76-
batch.each do |ip|
77-
next if @results[ip]
78-
79-
begin
80-
data = probe_pkt_ntp
81-
udp_sock.sendto(data, ip, datastore['RPORT'].to_i, 0)
82-
rescue ::Interrupt
83-
raise $!
84-
rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused
85-
nil
86-
end
87-
88-
if (idx % 30 == 0)
89-
while (r = udp_sock.recvfrom(65535, 0.1) and r[1])
90-
parse_reply(r)
91-
end
92-
end
93-
94-
idx += 1
95-
end
96-
end
97-
98-
while (r = udp_sock.recvfrom(65535, 10) and r[1])
99-
parse_reply(r)
100-
end
101-
102-
rescue ::Interrupt
103-
raise $!
104-
rescue ::Exception => e
105-
print_error("Unknown error: #{e.class} #{e}")
106-
end
107-
68+
# Called after the scan block
69+
def scanner_postscan(batch)
10870
@results.keys.each do |k|
71+
response_map = { @probe => @results[k][:messages] }
72+
peer = "#{k}:#{rport}"
10973

74+
# TODO: check to see if any of the responses are actually NTP before reporting
11075
report_service(
11176
:host => k,
11277
:proto => 'udp',
113-
:port => datastore['RPORT'].to_i,
78+
:port => rport,
11479
:name => 'ntp'
11580
)
11681

117-
report_note(
118-
:host => k,
119-
:proto => 'udp',
120-
:port => datastore['RPORT'].to_i,
121-
:type => 'ntp.monlist',
122-
:data => {:monlist => @results[k]}
123-
)
124-
125-
if (@aliases[k] and @aliases[k].keys[0] != k)
126-
print_good("#{k}:#{datastore['RPORT'].to_i} NTP monlist request permitted (#{@results[k].length} entries)")
82+
peers = @results[k][:peers].flatten(1)
83+
unless peers.empty?
84+
print_good("#{peer} NTP monlist request permitted (#{peers.length} entries)")
85+
# store the peers found from the monlist
86+
report_note(
87+
:host => k,
88+
:proto => 'udp',
89+
:port => rport,
90+
:type => 'ntp.monlist',
91+
:data => {:monlist => peers}
92+
)
93+
# print out peers if desired
94+
if datastore['SHOW_LIST']
95+
peers.each do |ntp_peer|
96+
print_status("#{peer} #{ntp_peer}")
97+
end
98+
end
99+
# store any aliases for our target
127100
report_note(
128101
:host => k,
129102
:proto => 'udp',
130-
:port => datastore['RPORT'].to_i,
103+
:port => rport,
131104
:type => 'ntp.addresses',
132-
:data => {:addresses => @aliases[k].keys}
105+
:data => {:addresses => peers.map { |p| p[1] }.sort.uniq }
133106
)
134-
end
135107

136-
if (datastore['StoreNTPClients'])
137-
print_status("#{k} Storing #{@results[k].length} NTP client hosts in the database...")
138-
@results[k].each do |r|
139-
maddr,mport,mserv = r
140-
report_note(
141-
:host => maddr,
142-
:type => 'ntp.client.history',
143-
:data => {
144-
:address => maddr,
145-
:port => mport,
146-
:server => mserv
147-
}
148-
)
108+
if (datastore['StoreNTPClients'])
109+
print_status("#{peer} Storing #{peers.length} NTP client hosts in the database...")
110+
peers.each do |r|
111+
maddr,mserv,mport = r
112+
report_note(
113+
:host => maddr,
114+
:type => 'ntp.client.history',
115+
:data => {
116+
:address => maddr,
117+
:port => mport,
118+
:server => mserv
119+
}
120+
)
121+
end
149122
end
150123
end
124+
125+
vulnerable, proof = prove_drdos(response_map)
126+
what = 'NTP Mode 7 monlist DRDoS (CVE-2013-5211)'
127+
if vulnerable
128+
print_good("#{peer} - Vulnerable to #{what}: #{proof}")
129+
report_vuln({
130+
:host => k,
131+
:port => rport,
132+
:proto => 'udp',
133+
:name => what,
134+
:refs => self.references
135+
})
136+
else
137+
vprint_status("#{peer} - Not vulnerable to #{what}: #{proof}")
138+
end
151139
end
152140

153141
end
154142

143+
def extract_peer_tuples(data)
144+
return [] if data.length < (72 + 16)
145+
146+
# NTP headers 8 bytes
147+
ntp_flags, ntp_auth, ntp_vers, ntp_code = data.slice!(0,4).unpack('C*')
148+
pcnt, plen = data.slice!(0,4).unpack('nn')
149+
return [] if plen != 72
150+
151+
idx = 0
152+
peer_tuples = []
153+
1.upto(pcnt) do
154+
#u_int32 firsttime; /* first time we received a packet */
155+
#u_int32 lasttime; /* last packet from this host */
156+
#u_int32 restr; /* restrict bits (was named lastdrop) */
157+
#u_int32 count; /* count of packets received */
158+
#u_int32 addr; /* host address V4 style */
159+
#u_int32 daddr; /* destination host address */
160+
#u_int32 flags; /* flags about destination */
161+
#u_short port; /* port number of last reception */
162+
163+
_,_,_,_,saddr,daddr,_,dport = data[idx, 30].unpack("NNNNNNNn")
164+
165+
peer_tuples << [ Rex::Socket.addr_itoa(saddr), Rex::Socket.addr_itoa(daddr), dport ]
166+
idx += plen
167+
end
168+
peer_tuples
169+
end
170+
171+
# Fingerprint a single host
155172
def parse_reply(pkt)
156173

157174
# Ignore "empty" packets
@@ -196,9 +213,4 @@ def parse_reply(pkt)
196213
idx += plen
197214
end
198215
end
199-
200-
def probe_pkt_ntp
201-
"\x17\x00\x03\x2a" + "\x00" * 188
202-
end
203-
204216
end

0 commit comments

Comments
 (0)