|
5 | 5 |
|
6 | 6 |
|
7 | 7 | require 'msf/core'
|
8 |
| -require 'openssl' |
9 |
| -require 'snmp' |
| 8 | +require 'metasploit/framework/community_string_collection' |
| 9 | +require 'metasploit/framework/login_scanner/snmp' |
10 | 10 |
|
11 | 11 | class Metasploit3 < Msf::Auxiliary
|
12 | 12 |
|
@@ -49,260 +49,68 @@ def run_batch_size
|
49 | 49 | # Operate on an entire batch of hosts at once
|
50 | 50 | def run_batch(batch)
|
51 | 51 |
|
52 |
| - @found = {} |
53 |
| - @tried = [] |
54 |
| - |
55 |
| - begin |
56 |
| - udp_sock = nil |
57 |
| - idx = 0 |
58 |
| - |
59 |
| - # Create an unbound UDP socket if no CHOST is specified, otherwise |
60 |
| - # create a UDP socket bound to CHOST (in order to avail of pivoting) |
61 |
| - udp_sock = Rex::Socket::Udp.create( { 'LocalHost' => datastore['CHOST'] || nil, 'Context' => {'Msf' => framework, 'MsfExploit' => self} }) |
62 |
| - add_socket(udp_sock) |
63 |
| - |
64 |
| - each_user_pass do |user, pass| |
65 |
| - comm = pass |
66 |
| - |
67 |
| - data1 = create_probe_snmp1(comm) |
68 |
| - data2 = create_probe_snmp2(comm) |
69 |
| - |
70 |
| - batch.each do |ip| |
71 |
| - fq_pass = [ip,pass] |
72 |
| - next if @tried.include? fq_pass |
73 |
| - @tried << fq_pass |
74 |
| - vprint_status "#{ip}:#{datastore['RPORT']} - SNMP - Trying #{(pass.nil? || pass.empty?) ? "<BLANK>" : pass}..." |
75 |
| - |
76 |
| - begin |
77 |
| - udp_sock.sendto(data1, ip, datastore['RPORT'].to_i, 0) |
78 |
| - udp_sock.sendto(data2, ip, datastore['RPORT'].to_i, 0) |
79 |
| - rescue ::Interrupt |
80 |
| - raise $! |
81 |
| - rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused |
82 |
| - nil |
83 |
| - end |
84 |
| - |
85 |
| - if (idx % 10 == 0) |
86 |
| - while (r = udp_sock.recvfrom(65535, 0.25) and r[1]) |
87 |
| - parse_reply(r) |
88 |
| - end |
89 |
| - end |
90 |
| - |
91 |
| - idx += 1 |
92 |
| - |
93 |
| - end |
94 |
| - end |
95 |
| - |
96 |
| - idx = 0 |
97 |
| - while (r = udp_sock.recvfrom(65535, 3) and r[1] and idx < 500) |
98 |
| - parse_reply(r) |
99 |
| - idx += 1 |
100 |
| - end |
101 |
| - |
102 |
| - if @found.keys.length > 0 |
103 |
| - print_status("Validating scan results from #{@found.keys.length} hosts...") |
104 |
| - end |
105 |
| - |
106 |
| - # Review all successful communities and determine write access |
107 |
| - @found.keys.sort.each do |host| |
108 |
| - fake_comm = Rex::Text.rand_text_alphanumeric(8) |
109 |
| - anycomm_ro = false |
110 |
| - anycomm_rw = false |
111 |
| - comms_ro = [] |
112 |
| - comms_rw = [] |
113 |
| - finished = false |
114 |
| - versions = ["1", "2"] |
115 |
| - |
116 |
| - versions.each do |version| |
117 |
| - comms_todo = @found[host].keys.sort |
118 |
| - comms_todo.unshift(fake_comm) |
119 |
| - |
120 |
| - comms_todo.each do |comm| |
121 |
| - begin |
122 |
| - sval = nil |
123 |
| - snmp = snmp_client(host, datastore['RPORT'].to_i, version, udp_sock, comm) |
124 |
| - resp = snmp.get("sysName.0") |
125 |
| - resp.each_varbind { |var| sval = var.value } |
126 |
| - next if not sval |
127 |
| - |
128 |
| - svar = ::SNMP::VarBind.new("1.3.6.1.2.1.1.5.0", ::SNMP::OctetString.new(sval)) |
129 |
| - resp = snmp.set(svar) |
130 |
| - |
131 |
| - if resp.error_status == :noError |
132 |
| - comms_rw << comm |
133 |
| - print_status("Host #{host} provides READ-WRITE access with community '#{comm}'") |
134 |
| - if comm == fake_comm |
135 |
| - anycomm_rw = true |
136 |
| - finished = true |
137 |
| - break |
138 |
| - end |
139 |
| - else |
140 |
| - comms_ro << comm |
141 |
| - print_status("Host #{host} provides READ-ONLY access with community '#{comm}'") |
142 |
| - if comm == fake_comm |
143 |
| - anycomm_ro = true |
144 |
| - finished = true |
145 |
| - break |
146 |
| - end |
147 |
| - end |
148 |
| - |
149 |
| - # Used to flag whether this version was compatible |
150 |
| - finished = true |
151 |
| - |
152 |
| - rescue ::SNMP::UnsupportedPduTag, ::SNMP::InvalidPduTag, ::SNMP::ParseError, |
153 |
| - ::SNMP::InvalidErrorStatus, ::SNMP::InvalidTrapVarbind, ::SNMP::InvalidGenericTrap, |
154 |
| - ::SNMP::BER::OutOfData, ::SNMP::BER::InvalidLength, ::SNMP::BER::InvalidTag, |
155 |
| - ::SNMP::BER::InvalidObjectId, ::SNMP::MIB::ModuleNotLoadedError, |
156 |
| - ::SNMP::UnsupportedValueTag |
157 |
| - next |
158 |
| - |
159 |
| - rescue ::SNMP::UnsupportedVersion |
160 |
| - break |
161 |
| - rescue ::SNMP::RequestTimeout |
162 |
| - next |
163 |
| - end |
164 |
| - end |
165 |
| - |
166 |
| - break if finished |
167 |
| - end |
168 |
| - |
169 |
| - # Report on the results |
170 |
| - comms_ro = ["anything"] if anycomm_ro |
171 |
| - comms_rw = ["anything"] if anycomm_rw |
| 52 | + batch.each do |ip| |
| 53 | + collection = Metasploit::Framework::CommunityStringCollection.new( |
| 54 | + pass_file: datastore['PASS_FILE'], |
| 55 | + password: datastore['PASSWORD'] |
| 56 | + ) |
172 | 57 |
|
173 |
| - comms_rw.each do |comm| |
174 |
| - report_auth_info( |
175 |
| - :host => host, |
176 |
| - :port => datastore['RPORT'].to_i, |
177 |
| - :proto => 'udp', |
178 |
| - :sname => 'snmp', |
179 |
| - :user => '', |
180 |
| - :pass => comm, |
181 |
| - :duplicate_ok => true, |
182 |
| - :active => true, |
183 |
| - :source_type => "user_supplied", |
184 |
| - :type => "password" |
185 |
| - ) |
186 |
| - end |
| 58 | + scanner = Metasploit::Framework::LoginScanner::SNMP.new( |
| 59 | + host: ip, |
| 60 | + port: rport, |
| 61 | + cred_details: collection, |
| 62 | + stop_on_success: datastore['STOP_ON_SUCCESS'], |
| 63 | + connection_timeout: 2 |
| 64 | + ) |
187 | 65 |
|
188 |
| - comms_ro.each do |comm| |
189 |
| - report_auth_info( |
190 |
| - :host => host, |
191 |
| - :port => datastore['RPORT'].to_i, |
192 |
| - :proto => 'udp', |
193 |
| - :sname => 'snmp', |
194 |
| - :user => '', |
195 |
| - :pass => comm, |
196 |
| - :duplicate_ok => true, |
197 |
| - :active => true, |
198 |
| - :source_type => "user_supplied", |
199 |
| - :type => "password_ro" |
200 |
| - ) |
| 66 | + service_data = { |
| 67 | + address: ip, |
| 68 | + port: rport, |
| 69 | + service_name: 'snmp', |
| 70 | + protocol: 'udp', |
| 71 | + workspace_id: myworkspace_id |
| 72 | + } |
| 73 | + |
| 74 | + scanner.scan! do |result| |
| 75 | + if result.success? |
| 76 | + credential_data = { |
| 77 | + module_fullname: self.fullname, |
| 78 | + origin_type: :service, |
| 79 | + username: result.credential.public |
| 80 | + } |
| 81 | + credential_data.merge!(service_data) |
| 82 | + |
| 83 | + credential_core = create_credential(credential_data) |
| 84 | + |
| 85 | + login_data = { |
| 86 | + core: credential_core, |
| 87 | + last_attempted_at: DateTime.now, |
| 88 | + status: Metasploit::Model::Login::Status::SUCCESSFUL |
| 89 | + } |
| 90 | + login_data.merge!(service_data) |
| 91 | + |
| 92 | + create_credential_login(login_data) |
| 93 | + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" |
| 94 | + else |
| 95 | + invalidate_data = { |
| 96 | + public: result.credential.public, |
| 97 | + private: result.credential.private, |
| 98 | + realm_key: result.credential.realm_key, |
| 99 | + realm_value: result.credential.realm, |
| 100 | + status: result.status |
| 101 | + } .merge(service_data) |
| 102 | + invalidate_login(invalidate_data) |
| 103 | + print_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})" |
201 | 104 | end
|
202 | 105 | end
|
203 |
| - |
204 |
| - rescue ::Interrupt |
205 |
| - raise $! |
206 |
| - rescue ::Exception => e |
207 |
| - print_error("Unknown error: #{e.class} #{e}") |
208 | 106 | end
|
209 |
| - |
210 | 107 | end
|
211 | 108 |
|
212 |
| - # |
213 |
| - # Allocate a SNMP client using the existing socket |
214 |
| - # |
215 |
| - def snmp_client(host, port, version, socket, community) |
216 |
| - version = :SNMPv1 if version == "1" |
217 |
| - version = :SNMPv2c if version == "2c" |
218 |
| - |
219 |
| - snmp = ::SNMP::Manager.new( |
220 |
| - :Host => host, |
221 |
| - :Port => port, |
222 |
| - :Community => community, |
223 |
| - :Version => version, |
224 |
| - :Timeout => 1, |
225 |
| - :Retries => 2, |
226 |
| - :Transport => SNMP::RexUDPTransport, |
227 |
| - :Socket => socket |
228 |
| - ) |
| 109 | + def rport |
| 110 | + datastore['RPORT'] |
229 | 111 | end
|
230 | 112 |
|
231 |
| - # |
232 |
| - # The response parsers |
233 |
| - # |
234 |
| - def parse_reply(pkt) |
235 |
| - |
236 |
| - return if not pkt[1] |
237 |
| - |
238 |
| - if(pkt[1] =~ /^::ffff:/) |
239 |
| - pkt[1] = pkt[1].sub(/^::ffff:/, '') |
240 |
| - end |
241 |
| - |
242 |
| - asn = OpenSSL::ASN1.decode(pkt[0]) rescue nil |
243 |
| - return if not asn |
244 | 113 |
|
245 |
| - snmp_error = asn.value[0].value rescue nil |
246 |
| - snmp_comm = asn.value[1].value rescue nil |
247 |
| - snmp_data = asn.value[2].value[3].value[0] rescue nil |
248 |
| - snmp_oid = snmp_data.value[0].value rescue nil |
249 |
| - snmp_info = snmp_data.value[1].value rescue nil |
250 | 114 |
|
251 |
| - return if not (snmp_error and snmp_comm and snmp_data and snmp_oid and snmp_info) |
252 |
| - snmp_info = snmp_info.to_s.gsub(/\s+/, ' ') |
253 |
| - |
254 |
| - inf = snmp_info |
255 |
| - com = snmp_comm |
256 |
| - |
257 |
| - if(com) |
258 |
| - @found[pkt[1]]||={} |
259 |
| - if(not @found[pkt[1]][com]) |
260 |
| - print_good("SNMP: #{pkt[1]} community string: '#{com}' info: '#{inf}'") |
261 |
| - @found[pkt[1]][com] = inf |
262 |
| - end |
263 |
| - |
264 |
| - report_service( |
265 |
| - :host => pkt[1], |
266 |
| - :port => pkt[2], |
267 |
| - :proto => 'udp', |
268 |
| - :name => 'snmp', |
269 |
| - :info => inf, |
270 |
| - :state => "open" |
271 |
| - ) |
272 |
| - end |
273 |
| - end |
274 |
| - |
275 |
| - |
276 |
| - def create_probe_snmp1(name) |
277 |
| - xid = rand(0x100000000) |
278 |
| - pdu = |
279 |
| - "\x02\x01\x00" + |
280 |
| - "\x04" + [name.length].pack('c') + name + |
281 |
| - "\xa0\x1c" + |
282 |
| - "\x02\x04" + [xid].pack('N') + |
283 |
| - "\x02\x01\x00" + |
284 |
| - "\x02\x01\x00" + |
285 |
| - "\x30\x0e\x30\x0c\x06\x08\x2b\x06\x01\x02\x01" + |
286 |
| - "\x01\x01\x00\x05\x00" |
287 |
| - head = "\x30" + [pdu.length].pack('C') |
288 |
| - data = head + pdu |
289 |
| - data |
290 |
| - end |
291 |
| - |
292 |
| - def create_probe_snmp2(name) |
293 |
| - xid = rand(0x100000000) |
294 |
| - pdu = |
295 |
| - "\x02\x01\x01" + |
296 |
| - "\x04" + [name.length].pack('c') + name + |
297 |
| - "\xa1\x19" + |
298 |
| - "\x02\x04" + [xid].pack('N') + |
299 |
| - "\x02\x01\x00" + |
300 |
| - "\x02\x01\x00" + |
301 |
| - "\x30\x0b\x30\x09\x06\x05\x2b\x06\x01\x02\x01" + |
302 |
| - "\x05\x00" |
303 |
| - head = "\x30" + [pdu.length].pack('C') |
304 |
| - data = head + pdu |
305 |
| - data |
306 |
| - end |
307 | 115 |
|
308 | 116 | end
|
0 commit comments