Skip to content

Commit 8fa81de

Browse files
committed
Fuzz mode 7 more correctly. Cleanup.
Provide empty 188-byte payload for mode 7 messages, otherwise nothing seems to response. Provide more useful defaults for versions/modes. Allow control over what mode 7 stuff is fuzzed.
1 parent 0352a53 commit 8fa81de

File tree

1 file changed

+55
-38
lines changed

1 file changed

+55
-38
lines changed

modules/auxiliary/fuzzers/ntp/ntp_protocol_fuzzer.rb

Lines changed: 55 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ class Metasploit3 < Msf::Auxiliary
1313
include Msf::Exploit::Remote::Udp
1414
include Msf::Auxiliary::Scanner
1515

16-
NTP_VERSIONS = (0..7).to_a
17-
NTP_MODES = (0..7).to_a
16+
NTP_SUPPORTED_VERSIONS = (0..7).to_a
17+
NTP_SUPPORTED_MODES = (0..7).to_a
18+
NTP_SUPPORTED_MODE_7_IMPLEMENTATIONS = (0..255).to_a
19+
NTP_SUPPORTED_MODE_7_REQUEST_CODES = (0..255).to_a
1820

1921
def initialize
2022
super(
@@ -49,8 +51,10 @@ def initialize
4951
register_options(
5052
[
5153
Opt::RPORT(123),
52-
OptString.new('VERSIONS', [true, 'Versions to fuzz', NTP_VERSIONS.join(',')]),
53-
OptString.new('MODES', [true, 'Versions to fuzz', NTP_MODES.join(',')]),
54+
OptString.new('VERSIONS', [true, 'Versions to fuzz', [3,2,4]]),
55+
OptString.new('MODES', [true, 'Modes to fuzz', NTP_SUPPORTED_MODES]),
56+
OptString.new('MODE_7_IMPLEMENTATIONS', [true, 'Mode 7 implementations to fuzz', [3,2,0]]),
57+
OptString.new('MODE_7_REQUEST_CODES', [true, 'Mode 7 request codes to fuzz', (0..45).to_a]),
5458
OptInt.new('SLEEP', [true, 'Sleep for this many ms between requests', 0]),
5559
OptInt.new('WAIT', [true, 'Wait this many ms for responses', 500])
5660
], self.class)
@@ -164,12 +168,20 @@ def sleep_time
164168
def run_host(ip)
165169
# parse and sanity check versions
166170
@versions = datastore['VERSIONS'].split(/[^\d]/).select { |v| !v.empty? }.map { |v| v.to_i }
167-
unsupported_versions = @versions - NTP_VERSIONS
171+
unsupported_versions = @versions - NTP_SUPPORTED_VERSIONS
168172
fail "Unsupported NTP versions: #{unsupported_versions}" unless unsupported_versions.empty?
169173
# parse and sanity check modes
170174
@modes = datastore['MODES'].split(/[^\d]/).select { |m| !m.empty? }.map { |v| v.to_i }
171-
unsupported_modes = @modes - NTP_MODES
175+
unsupported_modes = @modes - NTP_SUPPORTED_MODES
172176
fail "Unsupported NTP modes: #{unsupported_modes}" unless unsupported_modes.empty?
177+
# parse and sanity check mode 7 implementations
178+
@implementations = datastore['MODE_7_IMPLEMENTATIONS'].split(/[^\d]/).select { |m| !m.empty? }.map { |v| v.to_i }
179+
unsupported_implementations = @implementations - NTP_SUPPORTED_MODE_7_IMPLEMENTATIONS
180+
fail "Unsupported NTP mode 7implementations: #{unsupported_implementations}" unless unsupported_implementations.empty?
181+
# parse and sanity check mode 7 REQUEST_CODES
182+
@request_codes = datastore['MODE_7_REQUEST_CODES'].split(/[^\d]/).select { |m| !m.empty? }.map { |v| v.to_i }
183+
unsupported_request_codes = @request_codes - NTP_SUPPORTED_MODE_7_REQUEST_CODES
184+
fail "Unsupported NTP mode 7 request codes: #{unsupported_request_codes}" unless unsupported_request_codes.empty?
173185

174186
connect_udp
175187
fuzz_version_mode(ip)
@@ -183,32 +195,30 @@ def run_host(ip)
183195

184196
# Sends a series of NTP control messages
185197
def fuzz_control(host)
186-
@versions.map { |v| v.to_i }.each do |version|
198+
@versions.each do |version|
187199
print_status("#{host}:#{rport} fuzzing version #{version} control messages (mode 6)")
188200
0.upto(31) do |op|
189201
request = build_ntp_control(version, op)
190202
what = "#{request.size}-byte version #{version} mode 6 op #{op} message"
191203
vprint_status("#{host}:#{rport} probing with #{request.size}-byte #{what}")
192-
probe(host, datastore['RPORT'].to_i, request).each do |reply|
193-
handle_response(host, request, reply, what)
194-
end
204+
responses = probe(host, datastore['RPORT'].to_i, request)
205+
handle_responses(host, request, responses, what)
195206
Rex.sleep(sleep_time)
196207
end
197208
end
198209
end
199210

200211
# Sends a series of NTP private messages
201212
def fuzz_private(host)
202-
@versions.map { |v| v.to_i }.each do |version|
213+
@versions.each do |version|
203214
print_status("#{host}:#{rport} fuzzing version #{version} private messages (mode 7)")
204-
0.upto(255) do |implementation|
205-
0.upto(255) do |request_code|
206-
request = build_ntp_private(version, implementation, request_code)
215+
@implementations.each do |implementation|
216+
@request_codes.each do |request_code|
217+
request = build_ntp_private(version, implementation, request_code, "\x00"*188)
207218
what = "#{request.size}-byte version #{version} mode 7 imp #{implementation} req #{request_code} message"
208219
vprint_status("#{host}:#{rport} probing with #{request.size}-byte #{what}")
209-
probe(host, datastore['RPORT'].to_i, request).each do |reply|
210-
handle_response(host, request, reply, what)
211-
end
220+
responses = probe(host, datastore['RPORT'].to_i, request)
221+
handle_responses(host, request, responses, what)
212222
Rex.sleep(sleep_time)
213223
end
214224
end
@@ -220,11 +230,10 @@ def fuzz_short(host)
220230
print_status("#{host}:#{rport} fuzzing short messages")
221231
0.upto(4) do |size|
222232
request = SecureRandom.random_bytes(size)
223-
what = "Short #{request.size}-byte random message"
233+
what = "short #{request.size}-byte random message"
224234
vprint_status("#{host}:#{rport} probing with #{what}")
225-
probe(host, datastore['RPORT'].to_i, request).each do |reply|
226-
handle_response(host, request, reply, what)
227-
end
235+
responses = probe(host, datastore['RPORT'].to_i, request)
236+
handle_responses(host, request, responses, what)
228237
Rex.sleep(sleep_time)
229238
end
230239
end
@@ -236,25 +245,23 @@ def fuzz_random(host)
236245
request = SecureRandom.random_bytes(48)
237246
what = "random #{request.size}-byte message"
238247
vprint_status("#{host}:#{rport} probing with #{what}")
239-
probe(host, datastore['RPORT'].to_i, request).each do |reply|
240-
handle_response(host, request, reply, what)
241-
end
248+
responses = probe(host, datastore['RPORT'].to_i, request)
249+
handle_responses(host, request, responses, what)
242250
Rex.sleep(sleep_time)
243251
end
244252
end
245253

246254
# Sends a series of different version + mode combinations
247255
def fuzz_version_mode(host, short=false)
248256
print_status("#{host}:#{rport} fuzzing #{short ? 'short ' : nil}version and mode combinations")
249-
@versions.map { |v| v.to_i }.each do |version|
250-
@modes.map { |m| m.to_i }.each do |mode|
257+
@versions.each do |version|
258+
@modes.each do |mode|
251259
request = build_ntp_generic(version, mode)
252260
request = request[0, 4] if short
253261
what = "#{request.size}-byte #{short ? 'short ' : nil}version #{version} mode #{mode} message"
254262
vprint_status("#{host}:#{rport} probing with #{what}")
255-
probe(host, datastore['RPORT'].to_i, request).each do |reply|
256-
handle_response(host, request, reply, what)
257-
end
263+
responses = probe(host, datastore['RPORT'].to_i, request)
264+
handle_responses(host, request, responses, what)
258265
Rex.sleep(sleep_time)
259266
end
260267
end
@@ -276,19 +283,29 @@ def describe(message)
276283
"#{message.size}-byte version #{ntp.version} mode #{ntp.mode} reply"
277284
end
278285

279-
def handle_response(host, request, response, what)
280-
return unless response[1]
281-
data = response[0]
286+
def handle_responses(host, request, responses, what)
282287
problems = []
283-
problems << 'large response' if request.size < data.size
284-
ntp_req = NTPGeneric.new(request)
285-
ntp_resp = NTPGeneric.new(data)
286-
problems << 'version mismatch' if ntp_req.version != ntp_resp.version
288+
descriptions = []
289+
responses.select! { |r| r[1] }
290+
return if responses.empty?
291+
responses.each do |response|
292+
data = response[0]
293+
descriptions << describe(data)
294+
problems << 'large response' if request.size < data.size
295+
ntp_req = NTPGeneric.new(request)
296+
ntp_resp = NTPGeneric.new(data)
297+
problems << 'version mismatch' if ntp_req.version != ntp_resp.version
298+
end
299+
300+
problems << 'multiple responses' if responses.size > 1
301+
problems.sort!
302+
problems.uniq!
287303

304+
description = descriptions.join(',')
288305
if problems.empty?
289-
print_status("#{host}:#{rport} -- Received #{describe(data)} to #{what}")
306+
vprint_status("#{host}:#{rport} -- Received '#{description}' to #{what}")
290307
else
291-
print_good("#{host}:#{rport} -- Received #{describe(data)} to #{what}: #{problems.join(',')}")
308+
print_good("#{host}:#{rport} -- Received '#{description}' to #{what}: #{problems.join(',')}")
292309
end
293310
end
294311
end

0 commit comments

Comments
 (0)