Land #8185, Convert ntp modules to bindata
commit
47a659f554
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'bit-struct'
|
||||
require 'bindata'
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
|
@ -16,24 +16,25 @@ module NTP
|
|||
# pages 45/48 of http://tools.ietf.org/pdf/rfc1119.pdf
|
||||
# http://tools.ietf.org/html/rfc1305#appendix-D
|
||||
# http://tools.ietf.org/html/rfc5905#page-19
|
||||
class NTPGeneric < BitStruct
|
||||
class NTPGeneric < BinData::Record
|
||||
# 0 1 2 3
|
||||
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
# |LI | VN | mode| Stratum | Poll | Precision |
|
||||
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
unsigned :li, 2, default: 0
|
||||
unsigned :version, 3, default: 0
|
||||
unsigned :mode, 3, default: 0
|
||||
unsigned :stratum, 8, default: 0
|
||||
unsigned :poll, 8, default: 0
|
||||
unsigned :precision, 8, default: 0
|
||||
rest :payload
|
||||
endian :big
|
||||
bit2 :li
|
||||
bit3 :version
|
||||
bit3 :mode
|
||||
uint8 :stratum
|
||||
uint8 :poll
|
||||
uint8 :precision
|
||||
rest :payload
|
||||
end
|
||||
|
||||
# An NTP control message. Control messages are only specified for NTP
|
||||
# versions 2-4, but this is a fuzzer so why not try them all...
|
||||
class NTPControl < BitStruct
|
||||
class NTPControl < BinData::Record
|
||||
# 0 1 2 3
|
||||
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
@ -43,25 +44,26 @@ module NTP
|
|||
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
# | offset | count |
|
||||
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
unsigned :reserved, 2, default: 0
|
||||
unsigned :version, 3, default: 0
|
||||
unsigned :mode, 3, default: 6
|
||||
unsigned :response, 1, default: 0
|
||||
unsigned :error, 1, default: 0
|
||||
unsigned :more, 1, default: 0
|
||||
unsigned :operation, 5, default: 0
|
||||
unsigned :sequence, 16, default: 0
|
||||
unsigned :status, 16, default: 0
|
||||
unsigned :association_id, 16, default: 0
|
||||
endian :big
|
||||
bit2 :reserved
|
||||
bit3 :version
|
||||
bit3 :mode, initial_value: 6
|
||||
bit1 :response
|
||||
bit1 :error
|
||||
bit1 :more
|
||||
bit5 :operation
|
||||
uint16 :sequence
|
||||
uint16 :status
|
||||
uint16 :association_id
|
||||
# TODO: there *must* be bugs in the handling of these next two fields!
|
||||
unsigned :payload_offset, 16, default: 0
|
||||
unsigned :payload_size, 16, default: 0
|
||||
rest :payload
|
||||
uint16 :payload_offset
|
||||
uint16 :payload_size
|
||||
rest :payload
|
||||
end
|
||||
|
||||
# An NTP "private" message. Private messages are only specified for NTP
|
||||
# versions 2-4, but this is a fuzzer so why not try them all...
|
||||
class NTPPrivate < BitStruct
|
||||
class NTPPrivate < BinData::Record
|
||||
# 0 1 2 3
|
||||
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
@ -69,44 +71,46 @@ module NTP
|
|||
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
# | err | Number of data items | MBZ | Size of data item |
|
||||
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
unsigned :response, 1, default: 0
|
||||
unsigned :more, 1, default: 0
|
||||
unsigned :version, 3, default: 0
|
||||
unsigned :mode, 3, default: 7
|
||||
unsigned :auth, 1, default: 0
|
||||
unsigned :sequence, 7, default: 0
|
||||
unsigned :implementation, 8, default: 0
|
||||
unsigned :request_code, 8, default: 0
|
||||
unsigned :error, 4, default: 0
|
||||
unsigned :record_count, 12, default: 0
|
||||
unsigned :mbz, 4, default: 0
|
||||
unsigned :record_size, 12, default: 0
|
||||
rest :payload
|
||||
endian :big
|
||||
bit1 :response
|
||||
bit1 :more
|
||||
bit3 :version
|
||||
bit3 :mode, initial_value: 7
|
||||
bit1 :auth
|
||||
bit7 :sequence
|
||||
uint8 :implementation
|
||||
uint8 :request_code
|
||||
bit4 :error
|
||||
bit12 :record_count
|
||||
bit4 :mbz
|
||||
bit12 :record_size
|
||||
rest :payload
|
||||
|
||||
def records
|
||||
records = []
|
||||
1.upto(record_count) do |record_num|
|
||||
records << payload[record_size*(record_num-1), record_size]
|
||||
records << payload[record_size * (record_num - 1), record_size]
|
||||
end
|
||||
records
|
||||
end
|
||||
end
|
||||
|
||||
class NTPSymmetric < BitStruct
|
||||
unsigned :li, 2, default: 0
|
||||
unsigned :version, 3, default: 3
|
||||
unsigned :mode, 3, default: 0
|
||||
unsigned :stratum, 8, default: 0
|
||||
unsigned :poll, 8, default: 0
|
||||
unsigned :precision, 8, default: 0
|
||||
unsigned :root_delay, 32, default: 0
|
||||
unsigned :root_dispersion, 32, default: 0
|
||||
unsigned :reference_id, 32, default: 0
|
||||
unsigned :reference_timestamp, 64, default: 0
|
||||
unsigned :origin_timestamp, 64, default: 0
|
||||
unsigned :receive_timestamp, 64, default: 0
|
||||
unsigned :transmit_timestamp, 64, default: 0
|
||||
rest :payload
|
||||
class NTPSymmetric < BinData::Record
|
||||
endian :big
|
||||
bit2 :li
|
||||
bit3 :version, initial_value: 3
|
||||
bit3 :mode
|
||||
uint8 :stratum
|
||||
uint8 :poll
|
||||
uint8 :precision
|
||||
uint32 :root_delay
|
||||
uint32 :root_dispersion
|
||||
uint32 :reference_id
|
||||
uint64 :reference_timestamp
|
||||
uint64 :origin_timestamp
|
||||
uint64 :receive_timestamp
|
||||
uint64 :transmit_timestamp
|
||||
rest :payload
|
||||
end
|
||||
|
||||
def self.ntp_control(version, operation, payload = nil)
|
||||
|
@ -139,7 +143,7 @@ module NTP
|
|||
|
||||
# Parses the given message and provides a description about the NTP message inside
|
||||
def self.describe(message)
|
||||
ntp = NTPGeneric.new(message)
|
||||
ntp = NTPGeneric.new.read(message)
|
||||
"#{message.size}-byte version #{ntp.version} mode #{ntp.mode} reply"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -115,7 +115,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
print_status("#{host}:#{rport} fuzzing version #{version} private messages (mode 7)")
|
||||
@mode_7_implementations.each do |implementation|
|
||||
@mode_7_request_codes.each do |request_code|
|
||||
request = Rex::Proto::NTP.ntp_private(version, implementation, request_code, "\x00" * 188)
|
||||
request = Rex::Proto::NTP.ntp_private(version, implementation, request_code, "\0" * 188)
|
||||
what = "#{request.size}-byte version #{version} mode 7 imp #{implementation} req #{request_code} message"
|
||||
vprint_status("#{host}:#{rport} probing with #{request.size}-byte #{what}")
|
||||
responses = probe(host, datastore['RPORT'].to_i, request)
|
||||
|
@ -176,6 +176,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
# Sends +message+ to +host+ on UDP port +port+, returning all replies
|
||||
def probe(host, port, message)
|
||||
message = message.to_binary_s if message.respond_to?('to_binary_s')
|
||||
replies = []
|
||||
begin
|
||||
udp_sock.sendto(message, host, port, 0)
|
||||
|
@ -193,14 +194,15 @@ class MetasploitModule < Msf::Auxiliary
|
|||
def handle_responses(host, request, responses, what)
|
||||
problems = []
|
||||
descriptions = []
|
||||
request = request.to_binary_s if request.respond_to?('to_binary_s')
|
||||
responses.select! { |r| r[1] }
|
||||
return if responses.empty?
|
||||
responses.each do |response|
|
||||
data = response[0]
|
||||
descriptions << Rex::Proto::NTP.describe(data)
|
||||
problems << 'large response' if request.size < data.size
|
||||
ntp_req = Rex::Proto::NTP::NTPGeneric.new(request)
|
||||
ntp_resp = Rex::Proto::NTP::NTPGeneric.new(data)
|
||||
ntp_req = Rex::Proto::NTP::NTPGeneric.new.read(request)
|
||||
ntp_resp = Rex::Proto::NTP::NTPGeneric.new.read(data)
|
||||
problems << 'version mismatch' if ntp_req.version != ntp_resp.version
|
||||
end
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
# Called for each response packet
|
||||
def scanner_process(data, shost, sport)
|
||||
@results[shost] ||= { messages: [], peers: [] }
|
||||
@results[shost][:messages] << Rex::Proto::NTP::NTPPrivate.new(data)
|
||||
@results[shost][:messages] << Rex::Proto::NTP::NTPPrivate.new.read(data).to_binary_s
|
||||
@results[shost][:peers] << extract_peer_tuples(data)
|
||||
end
|
||||
|
||||
|
@ -55,7 +55,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
def scanner_prescan(batch)
|
||||
@results = {}
|
||||
@aliases = {}
|
||||
@probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 42)
|
||||
@probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 42, "\0" * 40).to_binary_s
|
||||
end
|
||||
|
||||
# Called after the scan block
|
||||
|
|
|
@ -81,11 +81,11 @@ class MetasploitModule < Msf::Auxiliary
|
|||
probe = build_crypto_nak(canary_timestamp)
|
||||
udp_sock.put(probe)
|
||||
|
||||
expected_length = probe.length - probe.payload.length
|
||||
expected_length = probe.to_binary_s.length - probe.payload.length
|
||||
response = udp_sock.timed_read(expected_length)
|
||||
disconnect_udp
|
||||
if response.length == expected_length
|
||||
ntp_symmetric = Rex::Proto::NTP::NTPSymmetric.new(response)
|
||||
ntp_symmetric = Rex::Proto::NTP::NTPSymmetric.new.read(response)
|
||||
if ntp_symmetric.mode == 2 && ntp_symmetric.origin_timestamp == canary_timestamp
|
||||
vprint_good("#{rhost}:#{rport} - NTP - VULNERABLE: Accepted a NTP symmetric active association")
|
||||
report_vuln(
|
||||
|
|
|
@ -36,13 +36,13 @@ class MetasploitModule < Msf::Auxiliary
|
|||
# Called before the scan block
|
||||
def scanner_prescan(batch)
|
||||
@results = {}
|
||||
@probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 0)
|
||||
@probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 0).to_binary_s
|
||||
end
|
||||
|
||||
# Called for each response packet
|
||||
def scanner_process(data, shost, sport)
|
||||
@results[shost] ||= []
|
||||
@results[shost] << Rex::Proto::NTP::NTPPrivate.new(data)
|
||||
@results[shost] << Rex::Proto::NTP::NTPPrivate.new.read(data).to_binary_s
|
||||
end
|
||||
|
||||
# Called after the scan block
|
||||
|
|
|
@ -36,13 +36,13 @@ class MetasploitModule < Msf::Auxiliary
|
|||
# Called for each response packet
|
||||
def scanner_process(data, shost, sport)
|
||||
@results[shost] ||= []
|
||||
@results[shost] << Rex::Proto::NTP::NTPPrivate.new(data)
|
||||
@results[shost] << Rex::Proto::NTP::NTPPrivate.new.read(data).to_binary_s
|
||||
end
|
||||
|
||||
# Called before the scan block
|
||||
def scanner_prescan(batch)
|
||||
@results = {}
|
||||
@probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 1)
|
||||
@probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 1).to_binary_s
|
||||
end
|
||||
|
||||
# Called after the scan block
|
||||
|
|
|
@ -35,7 +35,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
def scanner_process(data, shost, _sport)
|
||||
@results[shost] ||= []
|
||||
@results[shost] << Rex::Proto::NTP::NTPControl.new(data)
|
||||
@results[shost] << Rex::Proto::NTP::NTPControl.new.read(data)
|
||||
end
|
||||
|
||||
def scan_host(ip)
|
||||
|
|
|
@ -37,7 +37,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
# Called for each response packet
|
||||
def scanner_process(data, shost, sport)
|
||||
@results[shost] ||= []
|
||||
@results[shost] << Rex::Proto::NTP::NTPControl.new(data)
|
||||
@results[shost] << Rex::Proto::NTP::NTPControl.new.read(data)
|
||||
end
|
||||
|
||||
# Called before the scan block
|
||||
|
|
|
@ -38,13 +38,14 @@ class MetasploitModule < Msf::Auxiliary
|
|||
# Called for each response packet
|
||||
def scanner_process(data, shost, sport)
|
||||
@results[shost] ||= []
|
||||
@results[shost] << Rex::Proto::NTP::NTPPrivate.new(data)
|
||||
privmsg = Rex::Proto::NTP::NTPPrivate.new.read(data)
|
||||
@results[shost] << privmsg.to_binary_s
|
||||
end
|
||||
|
||||
# Called before the scan block
|
||||
def scanner_prescan(batch)
|
||||
@results = {}
|
||||
@probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 16)
|
||||
@probe = Rex::Proto::NTP.ntp_private(datastore['VERSION'], datastore['IMPLEMENTATION'], 16).to_binary_s
|
||||
end
|
||||
|
||||
# Called after the scan block
|
||||
|
|
|
@ -36,7 +36,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
# Called for each response packet
|
||||
def scanner_process(data, shost, sport)
|
||||
@results[shost] ||= []
|
||||
@results[shost] << Rex::Proto::NTP::NTPControl.new(data)
|
||||
@results[shost] << Rex::Proto::NTP::NTPControl.new.read(data)
|
||||
end
|
||||
|
||||
# Called before the scan block
|
||||
|
|
|
@ -24,11 +24,11 @@ RSpec.describe "Rex::Proto::NTP mode message handling" do
|
|||
end
|
||||
|
||||
it 'Generates control NTP messages correctly' do
|
||||
expect(@control_raw).to eq @control.to_s
|
||||
expect(@control_raw).to eq @control.to_binary_s
|
||||
end
|
||||
|
||||
it 'Parses control NTP messages correctly' do
|
||||
parsed_raw = Rex::Proto::NTP::NTPControl.new(@control_raw)
|
||||
parsed_raw = Rex::Proto::NTP::NTPControl.new.read(@control_raw)
|
||||
expect(@control).to eq parsed_raw
|
||||
end
|
||||
end
|
||||
|
@ -47,11 +47,11 @@ RSpec.describe "Rex::Proto::NTP mode message handling" do
|
|||
end
|
||||
|
||||
it 'Generates generic NTP messages correctly' do
|
||||
expect(@generic_raw).to eq @generic.to_s
|
||||
expect(@generic_raw).to eq @generic.to_binary_s
|
||||
end
|
||||
|
||||
it 'Parses generic NTP messages correctly' do
|
||||
parsed_raw = Rex::Proto::NTP::NTPGeneric.new(@generic_raw)
|
||||
parsed_raw = Rex::Proto::NTP::NTPGeneric.new.read(@generic_raw)
|
||||
expect(@generic).to eq parsed_raw
|
||||
end
|
||||
end
|
||||
|
@ -72,11 +72,11 @@ RSpec.describe "Rex::Proto::NTP mode message handling" do
|
|||
end
|
||||
|
||||
it 'Generates private NTP messages correctly' do
|
||||
expect(@private_raw).to eq @private.to_s
|
||||
expect(@private_raw).to eq @private.to_binary_s
|
||||
end
|
||||
|
||||
it 'Parses private NTP messages correctly' do
|
||||
parsed_raw = Rex::Proto::NTP::NTPPrivate.new(@private_raw)
|
||||
parsed_raw = Rex::Proto::NTP::NTPPrivate.new.read(@private_raw)
|
||||
expect(@private).to eq parsed_raw
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue