Skip to content

Commit ed3ccdc

Browse files
committed
Initial commit of modules for NTP vulns described in R7-2014-12
Not entirely functional or polished, but mostly working
1 parent 3307726 commit ed3ccdc

File tree

7 files changed

+662
-0
lines changed

7 files changed

+662
-0
lines changed

lib/msf/core/auxiliary/mixins.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@
2121
require 'msf/core/auxiliary/nmap'
2222
require 'msf/core/auxiliary/jtr'
2323
require 'msf/core/auxiliary/iax2'
24+
require 'msf/core/auxiliary/ntp'
2425
require 'msf/core/auxiliary/pii'

lib/msf/core/auxiliary/ntp.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# -*- coding: binary -*-
2+
require 'rex/proto/ntp'
3+
4+
module Msf
5+
6+
###
7+
#
8+
# This module provides methods for working with NTP
9+
#
10+
###
11+
module Auxiliary::NTP
12+
13+
include Auxiliary::Scanner
14+
15+
#
16+
# Initializes an instance of an auxiliary module that uses NTP
17+
#
18+
19+
def initialize(info = {})
20+
super
21+
register_options(
22+
[
23+
Opt::RPORT(123),
24+
], self.class)
25+
26+
register_advanced_options(
27+
[
28+
OptString.new('VERSIONS', [true, 'Try these NTP versions', '2,3']),
29+
OptString.new('IMPLEMENTATIONS', [true, 'Try these NTP mode 7 implementations', '3,2'])
30+
], self.class)
31+
end
32+
end
33+
end
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
##
2+
# This module requires Metasploit: http//metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
8+
class Metasploit3 < Msf::Auxiliary
9+
10+
include Msf::Auxiliary::Report
11+
include Msf::Exploit::Remote::Udp
12+
include Msf::Auxiliary::UDPScanner
13+
include Msf::Auxiliary::NTP
14+
15+
def initialize
16+
super(
17+
'Name' => 'NTP PEER_LIST DoS Scanner',
18+
'Description' => %q{
19+
This module identifies NTP servers which permit "PEER_LIST" queries and
20+
return responses that are larger in size or greater in quantity than
21+
the request, allowing remote attackers to cause a denial of service
22+
(traffic amplification) via spoofed requests.
23+
},
24+
'References' =>
25+
[
26+
],
27+
'Author' => 'Jon Hart <jon_hart[at]rapid7.com>',
28+
'License' => MSF_LICENSE
29+
)
30+
31+
register_options(
32+
[
33+
OptBool.new('SHOW_PEERS', [false, 'Show peers', 'false'])
34+
], self.class)
35+
end
36+
37+
# Called for each IP in the batch
38+
def scan_host(ip)
39+
@probes.each do |probe|
40+
scanner_send(probe, ip, datastore['RPORT'])
41+
end
42+
end
43+
44+
# Called before the scan block
45+
def scanner_prescan(batch)
46+
# build a probe for all possible variations of the PEER_LIST request, which
47+
# means using all combinations of NTP version, mode 7 implementations and
48+
# with and without payloads.
49+
@probes = []
50+
versions = datastore['VERSIONS'].split(/,/).map { |v| v.strip.to_i }
51+
implementations = datastore['IMPLEMENTATIONS'].split(/,/).map { |i| i.strip.to_i }
52+
payloads = ['', "\x00"*40]
53+
versions.each do |v|
54+
implementations.each do |i|
55+
payloads.each do |p|
56+
@probes << Rex::Proto::NTP.ntp_private(v, i, 0, p)
57+
end
58+
end
59+
end
60+
@results = {}
61+
vprint_status("Sending #{@probes.size} NTP PEER_LIST probes to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)")
62+
end
63+
64+
# Called for each response packet
65+
def scanner_process(data, shost, sport)
66+
this_peer = "#{shost}:#{sport}"
67+
# sanity check that the reply is not overly large/small
68+
if data.length < 8
69+
print_error("#{this_peer} -- suspiciously small (#{data.length} bytes) NTP PEER_LIST response")
70+
return
71+
elsif data.length > 500
72+
print_error("#{this_peer} -- suspiciously large (#{data.length} bytes) NTP PEER_LIST response")
73+
return
74+
end
75+
76+
# try to parse this response, alerting and aborting if it is invalid
77+
response = Rex::Proto::NTP::NTPPrivate.new(data)
78+
unless contains_relevant_message?(response, @probes)
79+
print_error("#{this_peer} -- unexpected NTP PEER_LIST response: #{response.inspect}")
80+
return
81+
end
82+
83+
if response.error != 0
84+
vprint_status("#{this_peer} -- error #{response.error} response to NTP PEER_LIST request")
85+
return
86+
end
87+
88+
if response.record_size != 32 || response.record_count == 0 || response.record_count > 15
89+
print_error("#{this_peer} -- suspicious NTP PEER_LIST response with #{response.record_count} #{response.record_size}-byte entries: #{response.inspect}")
90+
return
91+
end
92+
93+
these_results = []
94+
response.records.each do |record|
95+
# TODO: Rex this
96+
# u_int32 addr; /* address of peer */
97+
# u_short port; /* port number of peer */
98+
# u_char hmode; /* mode for this peer */
99+
# u_char flags; /* flags (from above) */
100+
# u_int v6_flag; /* is this v6 or not */
101+
# u_int unused1; /* (unused) padding for addr6 */
102+
# struct in6_addr addr6; /* v6 address of peer */
103+
104+
src_addr4, src_port = record[0,6].unpack('Nn')
105+
is_v6 = record[8,4].unpack('N').first
106+
107+
if is_v6 == 0
108+
src_addr = Rex::Socket.addr_itoa(src_addr4)
109+
else
110+
# XXX: according to the struct, this should be record[16,16], but that doesn't work. Why?
111+
src_addr6_parts = record[12, 16].unpack("N*")
112+
src_addr6 = 0
113+
0.upto(3) do |off|
114+
src_addr6 = src_addr6 | src_addr6_parts[off]
115+
src_addr6 <<= 32
116+
end
117+
src_addr = Rex::Socket.addr_itoa(src_addr6, true)
118+
end
119+
these_results << [ src_addr, src_port ]
120+
if datastore['SHOW_LIST']
121+
print_status("#{this_peer} peers with #{src_addr}:#{src_port}")
122+
end
123+
end
124+
125+
@results[shost] ||= []
126+
@results[shost] << these_results
127+
128+
end
129+
130+
# Called after the scan block
131+
def scanner_postscan(batch)
132+
@results.keys.each do |k|
133+
packets = @results[k]
134+
peers = packets.flatten(1)
135+
print_good("#{k}:#{rport} NTP PEER_LIST request permitted (#{packets.size} packets with #{peers.size} peers total)")
136+
report_service(
137+
:host => k,
138+
:proto => 'udp',
139+
:port => rport,
140+
:name => 'ntp'
141+
)
142+
143+
report_note(
144+
:host => k,
145+
:proto => 'udp',
146+
:port => rport,
147+
:type => 'ntp.peer_list',
148+
:data => {:peer_list => @results[k]}
149+
)
150+
end
151+
end
152+
end
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
##
2+
# This module requires Metasploit: http//metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
8+
class Metasploit3 < Msf::Auxiliary
9+
10+
include Msf::Auxiliary::Report
11+
include Msf::Exploit::Remote::Udp
12+
include Msf::Auxiliary::UDPScanner
13+
include Msf::Auxiliary::NTP
14+
15+
def initialize
16+
super(
17+
'Name' => 'NTP PEER_LIST_SUM DoS Scanner',
18+
'Description' => %q{
19+
This module identifies NTP servers which permit "PEER_LIST_SUM" queries and
20+
return responses that are larger in size or greater in quantity than
21+
the request, allowing remote attackers to cause a denial of service
22+
(traffic amplification) via spoofed requests.
23+
},
24+
'References' =>
25+
[
26+
],
27+
'Author' => 'Jon Hart <jon_hart[at]rapid7.com>',
28+
'License' => MSF_LICENSE
29+
)
30+
31+
register_options(
32+
[
33+
OptBool.new('SHOW_PEERS', [false, 'Show peers', 'false'])
34+
], self.class)
35+
end
36+
37+
# Called for each IP in the batch
38+
def scan_host(ip)
39+
scanner_send(@probe, ip, datastore['RPORT'])
40+
end
41+
42+
# Called for each response packet
43+
def scanner_process(data, shost, sport)
44+
this_peer = "#{shost}:#{sport}"
45+
# sanity check that the reply is not overly large/small
46+
if data.length < 8
47+
print_error("#{this_peer} -- suspiciously small (#{data.length} bytes) NTP PEER_LIST_SUM response")
48+
return
49+
elsif data.length > 500
50+
print_error("#{this_peer} -- suspiciously large (#{data.length} bytes) NTP PEER_LIST_SUM response")
51+
return
52+
end
53+
54+
# try to parse this response, alerting and aborting if it is invalid
55+
response = Rex::Proto::NTP::NTPPrivate.new(data)
56+
unless [ @version, @implementation, @request_code ] == [ response.version, response.implementation, response.request_code ]
57+
print_error("#{this_peer} -- unexpected NTP PEER_LIST_SUM response: #{response.inspect}")
58+
return
59+
end
60+
61+
if response.error != 0
62+
vprint_status("#{this_peer} -- error #{response.error} response to NTP PEER_LIST request")
63+
return
64+
end
65+
66+
if response.record_size != 72 || response.record_count == 0 || response.record_count > 9
67+
print_error("#{this_peer} -- suspicious NTP PEER_LIST_SUM response with #{response.record_count} #{response.record_size}-byte entries: #{response.inspect}")
68+
return
69+
end
70+
71+
these_results = []
72+
response.records.each do |record|
73+
# TODO: Rex this
74+
# u_int32 dstadr; /* local address (zero for undetermined) */
75+
# u_int32 srcadr; /* source address */
76+
# u_short srcport; /* source port */
77+
# u_char stratum; /* stratum of peer */
78+
# s_char hpoll; /* host polling interval */
79+
# s_char ppoll; /* peer polling interval */
80+
# u_char reach; /* reachability register */
81+
# u_char flags; /* flags, from above */
82+
# u_char hmode; /* peer mode */
83+
# s_fp delay; /* peer.estdelay */ (int32)
84+
# l_fp offset; /* peer.estoffset */ (2x int32)
85+
# u_fp dispersion; /* peer.estdisp */ (u_int32)
86+
# u_int v6_flag; /* is this v6 or not */
87+
# u_int unused1; /* (unused) padding for dstadr6 */
88+
# struct in6_addr dstadr6; /* local address (v6) */
89+
# struct in6_addr srcadr6; /* source address (v6) */
90+
91+
dst_addr4, src_addr4, src_port, stratum = record[0,12].unpack('NNnC')
92+
is_v6 = record[32,4].unpack('N').first
93+
94+
if is_v6 == 0
95+
src_addr = Rex::Socket.addr_itoa(src_addr4)
96+
else
97+
# XXX: is there a better way to do this?
98+
src_addr6_parts = record[52, 16].unpack("N*")
99+
src_addr6 = 0
100+
0.upto(3) do |off|
101+
src_addr6 = src_addr6 | src_addr6_parts[off]
102+
src_addr6 <<= 32
103+
end
104+
src_addr = Rex::Socket.addr_itoa(src_addr6, true)
105+
end
106+
these_results << [ src_addr, src_port, stratum]
107+
if datastore['SHOW_LIST']
108+
print_status("#{this_peer} peers with #{src_addr}:#{src_port} (stratum #{stratum})")
109+
end
110+
end
111+
112+
@results[shost] ||= []
113+
@results[shost] << these_results
114+
115+
end
116+
117+
# Called before the scan block
118+
def scanner_prescan(batch)
119+
@results = {}
120+
@version = 2
121+
@implementation = 3
122+
@request_code = 1
123+
@probe = Rex::Proto::NTP.ntp_private(@version, @implementation, @request_code)
124+
vprint_status("Sending probes to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)")
125+
end
126+
127+
# Called after the scan block
128+
def scanner_postscan(batch)
129+
@results.keys.each do |k|
130+
packets = @results[k]
131+
peers = packets.flatten(1)
132+
print_good("#{k}:#{rport} NTP PEER_LIST_SUM request permitted (#{packets.size} packets with #{peers.size} peers total)")
133+
report_service(
134+
:host => k,
135+
:proto => 'udp',
136+
:port => rport,
137+
:name => 'ntp'
138+
)
139+
140+
report_note(
141+
:host => k,
142+
:proto => 'udp',
143+
:port => rport,
144+
:type => 'ntp.peer_list_sum',
145+
:data => {:peer_list_sum => @results[k]}
146+
)
147+
end
148+
end
149+
end

0 commit comments

Comments
 (0)