Class: Metasploit::Framework::LoginScanner::SNMP
- Inherits:
-
Object
- Object
- Metasploit::Framework::LoginScanner::SNMP
- Includes:
- Base
- Defined in:
- lib/metasploit/framework/login_scanner/snmp.rb
Overview
This is the LoginScanner class for dealing with SNMP. It is responsible for taking a single target, and a list of credentials and attempting them. It then saves the results.
Constant Summary collapse
- DEFAULT_TIMEOUT =
2
- DEFAULT_PORT =
161
- DEFAULT_PROTOCOL =
'udp'.freeze
- DEFAULT_VERSION =
'1'.freeze
- DEFAULT_QUEUE_SIZE =
100
- LIKELY_PORTS =
[ 161, 162 ].freeze
- LIKELY_SERVICE_NAMES =
[ 'snmp' ].freeze
- PRIVATE_TYPES =
[ :password ].freeze
- REALM_KEY =
nil
Instance Attribute Summary collapse
-
#protocol ⇒ String
The SNMP protocol to use.
-
#queue_size ⇒ Integer
The number of logins to try in each batch.
-
#queued_credentials ⇒ Object
:nodoc: # :nodoc: # :nodoc:.
-
#queued_results ⇒ Object
:nodoc: # :nodoc: # :nodoc:.
-
#sock ⇒ Object
:nodoc: # :nodoc: # :nodoc:.
-
#version ⇒ String
The SNMP version to scan.
Instance Method Summary collapse
-
#configure_socket ⇒ Object
Create a new socket for this scanner.
-
#create_snmp_read_sys_descr_request(version_str, community) ⇒ Object
Create a SNMP request that tries to read from sys.sysDescr.0.
-
#create_snmp_write_sys_descr_request(version_str, community, data) ⇒ Object
Create a SNMP request that tries to write to sys.sysDescr.0.
-
#parse_snmp_response(pkt) ⇒ Object
Parse a SNMP reply from a packet and return a response hash or nil.
-
#process_logins(opts = {}) ⇒ Object
Queue up and possibly send any requests, based on the queue limit and final flag.
-
#process_responses(timeout = 1.0) ⇒ Object
Process any responses on the UDP socket and queue the results.
- #recv_wrapper(sock, max_size, timeout) ⇒ Object
-
#scan! {|result| ... } ⇒ void
Attempt to login with every credential in # #cred_details.
-
#send_snmp_read_request(version, community) ⇒ Object
Create and send a SNMP read request for sys.sysDescr.0.
-
#send_snmp_request(pkt) ⇒ Object
Send a SNMP request on the existing socket.
-
#send_snmp_write_request(version, community, data) ⇒ Object
Create and send a SNMP write request for sys.sysDescr.0.
- #send_wrapper(sock, pkt, host, port, flags) ⇒ Object
-
#set_sane_defaults ⇒ Object
Sets the SNMP parameters if not specified.
-
#shutdown_socket ⇒ Object
Close any open socket if it exists.
-
#versions ⇒ Array
This method returns an array of versions to scan.
Instance Attribute Details
#protocol ⇒ String
The SNMP protocol to use
33 34 35 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 33 def protocol @protocol end |
#queue_size ⇒ Integer
The number of logins to try in each batch
37 38 39 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 37 def queue_size @queue_size end |
#queued_credentials ⇒ Object
:nodoc: # :nodoc: # :nodoc:
25 26 27 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 25 def queued_credentials @queued_credentials end |
#queued_results ⇒ Object
:nodoc: # :nodoc: # :nodoc:
25 26 27 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 25 def queued_results @queued_results end |
#sock ⇒ Object
:nodoc: # :nodoc: # :nodoc:
25 26 27 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 25 def sock @sock end |
#version ⇒ String
The SNMP version to scan
29 30 31 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 29 def version @version end |
Instance Method Details
#configure_socket ⇒ Object
Create a new socket for this scanner
385 386 387 388 389 390 391 392 393 394 395 396 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 385 def configure_socket shutdown_socket if sock self.sock = ::Rex::Socket.create({ 'PeerHost' => host, 'PeerPort' => port, 'Proto' => protocol, 'Timeout' => connection_timeout, 'Context' => { 'Msf' => framework, 'MsfExploit' => framework_module } }) end |
#create_snmp_read_sys_descr_request(version_str, community) ⇒ Object
Create a SNMP request that tries to read from sys.sysDescr.0
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 298 def create_snmp_read_sys_descr_request(version_str, community) version = version_str == :SNMPv1 ? 1 : 2 OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(version - 1), OpenSSL::ASN1::OctetString(community), OpenSSL::ASN1::Set.new([ OpenSSL::ASN1::Integer(rand(0x80000000)), OpenSSL::ASN1::Integer(0), OpenSSL::ASN1::Integer(0), OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Sequence([ OpenSSL::ASN1.ObjectId('1.3.6.1.2.1.1.1.0'), OpenSSL::ASN1.Null(nil) ]) ]), ], 0, :IMPLICIT) ]).to_der end |
#create_snmp_write_sys_descr_request(version_str, community, data) ⇒ Object
Create a SNMP request that tries to write to sys.sysDescr.0
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 318 def create_snmp_write_sys_descr_request(version_str, community, data) version = version_str == :SNMPv1 ? 1 : 2 snmp_write = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(version - 1), OpenSSL::ASN1::OctetString(community), OpenSSL::ASN1::Set.new([ OpenSSL::ASN1::Integer(rand(0x80000000)), OpenSSL::ASN1::Integer(0), OpenSSL::ASN1::Integer(0), OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Sequence([ OpenSSL::ASN1.ObjectId('1.3.6.1.2.1.1.1.0'), OpenSSL::ASN1::OctetString(data) ]) ]), ], 3, :IMPLICIT) ]).to_der end |
#parse_snmp_response(pkt) ⇒ Object
Parse a SNMP reply from a packet and return a response hash or nil
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 338 def parse_snmp_response(pkt) asn = begin OpenSSL::ASN1.decode(pkt) rescue StandardError nil end return if !asn snmp_vers = begin asn.value[0].value.to_i rescue StandardError nil end snmp_comm = begin asn.value[1].value rescue StandardError nil end snmp_error = begin asn.value[2].value[1].value.to_i rescue StandardError nil end snmp_data = begin asn.value[2].value[3].value[0] rescue StandardError nil end snmp_oid = begin snmp_data.value[0].value rescue StandardError nil end snmp_info = begin snmp_data.value[1].value.to_s rescue StandardError nil end return if !(snmp_error && snmp_comm && snmp_data && snmp_oid && snmp_info) snmp_vers = snmp_vers == 0 ? '1' : '2c' { error: snmp_error, community: snmp_comm, proof: snmp_info, version: snmp_vers } end |
#process_logins(opts = {}) ⇒ Object
Queue up and possibly send any requests, based on the queue limit and final flag
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 179 def process_logins(opts = {}) self.queued_results ||= [] self.queued_credentials ||= [] unless opts[:final] || self.queued_credentials.length > queue_size self.queued_credentials.push [ opts[:type], opts[:community], opts[:version], opts[:data] ] return end return if self.queued_credentials.empty? process_responses(0.01) until self.queued_credentials.empty? action, community, version, data = self.queued_credentials.pop case action when 'read' send_snmp_read_request(version, community) when 'write' send_snmp_write_request(version, community, data) end sleep_between_attempts end process_responses(1.0) end |
#process_responses(timeout = 1.0) ⇒ Object
Process any responses on the UDP socket and queue the results
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 222 def process_responses(timeout = 1.0) queue = [] while (res = recv_wrapper(sock, 65535, timeout)) # Ignore invalid responses break if !(res[1]) # Ignore empty responses next if !(res[0] && !res[0].empty?) # Trim the IPv6-compat prefix off if needed shost = res[1].sub(/^::ffff:/, '') response = parse_snmp_response(res[0]) next unless response self.queued_results << { community: response[:community], host: host, port: port, protocol: protocol, service_name: 'snmp', proof: response[:proof], status: Metasploit::Model::Login::Status::SUCCESSFUL, access_level: 'read-only', snmp_version: response[:version], snmp_error: response[:error] } end end |
#recv_wrapper(sock, max_size, timeout) ⇒ Object
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 205 def recv_wrapper(sock, max_size, timeout) res = nil if protocol == 'udp' res = sock.recvfrom(max_size, timeout) elsif protocol == 'tcp' ready = ::IO.select([sock], nil, nil, timeout) if ready res = sock.recv_nonblock(max_size) # Put into an array to mimic recvfrom res = [res, host, port] end end res end |
#scan! {|result| ... } ⇒ void
This method returns an undefined value.
Attempt to login with every credential in # #cred_details.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 76 def scan! valid! # Keep track of connection errors. # If we encounter too many, we will stop. consecutive_error_count = 0 total_error_count = 0 successful_users = Set.new first_attempt = true # Create a socket for the initial login tests (read-only) configure_socket # Create a map of community name to credential object credential_map = {} begin each_credential do |credential| # Track the credentials by community string credential_map[credential.public] = credential # Skip users for whom we've have already found a password if successful_users.include?(credential.public) # For Pro bruteforce Reuse and Guess we need to note that we # skipped an attempt. if credential.parent.respond_to?(:skipped) credential.parent.skipped = true credential.parent.save! end next end # Queue and trigger authentication if queue size is reached versions.each do |version| process_logins(community: credential.public, type: 'read', version: version) end # Exit early if we already have a positive result if stop_on_success && !queued_results.empty? break end end rescue Errno::ECONNREFUSED # Exit early if we get an ICMP port unreachable return end # Handle any unprocessed responses process_logins(final: true) # Create a non-duplicated set of credentials found_credentials = queued_results.uniq # Reset the queued results for our write test self.queued_results = [] # Grab a new socket to avoid stale replies configure_socket # Try to write back the originally received values found_credentials.each do |result| process_logins( version: result[:snmp_version], community: result[:community], type: 'write', data: result[:proof] ) end # Catch any stragglers process_logins(final: true) # Mark any results from our write scan as read-write in our found credentials queued_results.select { |r| [0, 17].include? r[:snmp_error] }.map { |r| r[:community] }.uniq.each do |c| found_credentials.select { |r| r[:community] == c }.each do |result| result[:access_level] = 'read-write' end end # Iterate the results found_credentials.each do || # Scrub the SNMP version & error code from the tracked result .delete(:snmp_version) .delete(:snmp_error) # Associate the community with the original credential [:credential] = credential_map[.delete(:community)] # In the rare chance that we got a result for a community we didn't scan... next unless [:credential] # Create, freeze, and yield the result result = ::Metasploit::Framework::LoginScanner::Result.new() result.freeze yield result if block_given? end nil ensure shutdown_socket end |
#send_snmp_read_request(version, community) ⇒ Object
Create and send a SNMP read request for sys.sysDescr.0
254 255 256 257 258 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 254 def send_snmp_read_request(version, community) send_snmp_request( create_snmp_read_sys_descr_request(version, community) ) end |
#send_snmp_request(pkt) ⇒ Object
Send a SNMP request on the existing socket
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 278 def send_snmp_request(pkt) resend_count = 0 begin send_wrapper(sock, pkt, host, port, 0) rescue ::Errno::ENOBUFS resend_count += 1 if resend_count > MAX_RESEND_COUNT return false end ::IO.select(nil, nil, nil, 0.25) retry rescue ::Rex::ConnectionError # This fires for host unreachable, net unreachable, and broadcast sends # We can safely ignore all of these for UDP sends end end |
#send_snmp_write_request(version, community, data) ⇒ Object
Create and send a SNMP write request for sys.sysDescr.0
261 262 263 264 265 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 261 def send_snmp_write_request(version, community, data) send_snmp_request( create_snmp_write_sys_descr_request(version, community, data) ) end |
#send_wrapper(sock, pkt, host, port, flags) ⇒ Object
267 268 269 270 271 272 273 274 275 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 267 def send_wrapper(sock, pkt, host, port, flags) if protocol == 'tcp' return sock.send(pkt, flags) end if protocol == 'udp' return sock.sendto(pkt, host, port, 0) end end |
#set_sane_defaults ⇒ Object
Sets the SNMP parameters if not specified
405 406 407 408 409 410 411 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 405 def set_sane_defaults self.connection_timeout = DEFAULT_TIMEOUT if connection_timeout.nil? self.protocol = DEFAULT_PROTOCOL if protocol.nil? self.port = DEFAULT_PORT if port.nil? self.version = DEFAULT_VERSION if version.nil? self.queue_size = DEFAULT_QUEUE_SIZE if queue_size.nil? end |
#shutdown_socket ⇒ Object
Close any open socket if it exists
399 400 401 402 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 399 def shutdown_socket sock.close if sock self.sock = nil end |
#versions ⇒ Array
This method returns an array of versions to scan
60 61 62 63 64 65 66 67 68 69 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 60 def versions case version when '1' [:SNMPv1] when '2c' [:SNMPv2c] when 'all' %i[SNMPv1 SNMPv2c] end end |