1
mirror of https://github.com/rapid7/metasploit-framework synced 2024-11-05 14:57:30 +01:00

Land #8185, Convert ntp modules to bindata

This commit is contained in:
William Webb 2017-06-22 09:37:58 -05:00
commit 47a659f554
No known key found for this signature in database
GPG Key ID: 341763D0308DA650
11 changed files with 83 additions and 76 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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