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